July 16

Calculator with React Pt. 2:

So, I’ve made some huge changes since part 1. Since I’ve redesigned how the calculator looks and how it works, I’ve decided to just make a blog post about the finished calculator and how it works. Code is available on Github.

So first I needed to make it look better. Even though I think we all can appreciate that good ol’ Windows 98 look, it needed to be better. So this is how it looked before:

Animated GIF

And this is how it looks now! Even got a division button!

I won’t go into depth about how I made it look like that, but the CSS files are available on the GitHub page!

So let me show you how I made this!

First I make a state in my main App:


export default class App extends React.Component {

  constructor(props) {
    super(props)

    this.state = {

      isNumber1: true,
      number1: "0",
      operator: "",
      number2: "0"

    }
  }

I’m going to use the “isNumber1” bool to check if the user is inputting on the first or second number. “number1” and “number2” is for holding our input numbers and operator is for deciding what operation we are gonna do.

The “Button” component I created in part 1 is still mostly the same. I only added support for orange color and made the render function return only the button, so the the CSS grid would work with my buttons. I use the CSS grid to layout my buttons. Here’s the “Button” component:

Full code for Button component

import React, { Component } from 'react'
import propTypes from 'prop-types'
import './Button.css';

class Button extends Component {

    constructor(props) {
        super(props);

        this.buttonValue = props.buttonValue;

        //Set Button styling
        let p = props;

        this.className = "component-button";

        //Wide or small
        if (p.wide) {
            this.className += " wide"
        } else if (p.small) {
            this.className += " small";
        }

        //Colors
        // eslint-disable-next-line default-case
        switch (p.color) {
            case "blue":
                this.className += " blue";
                break;

            case "red":
                this.className += " red";
                break;

            case "orange":
                this.className += " orange";
                break;

        }

    }

    render() {
        return <button className={this.className} onClick={() => this.props.clickHandler(this.buttonValue)} >{this.buttonValue}</button>

    }
}

// Prop types
Button.propTypes = {
    wide: propTypes.bool,
    color: propTypes.string
}


export default Button

[collapse]

In part 1 I made a main clickHandler that I passed to every Button component, and it just decided what other handler to call depending on the button value it recieved. But I figured, why do that? I can just pass the correct clickHandler as a prop to the different Button components. That way it’s both cleaner, easier and I get rid of an entirely useless function.

I also changed how I added numbers to the calculator. Before I would multiply the current number with 10 and add whatever button value that was pressed, but since this is JavaScipt (a loosely typed language) I can just treat the number as a string until I need to do any calculation with it! Because of that, I can just add the button value as a string to the number. Easy! I also refactored how I check which number to add it to, by using a ternary operator and a new trick I found for the “setState” function.


  addNumber = number => {

    // Decide which number should be changed
    let changeNumber = this.state.isNumber1 ? "number1" : "number2";

    let newNumber = 0;

    // Check if a zero should be replaced by a number
    if (this.state.isNumber1) {
      newNumber = (this.state.number1 === "0") ? number.toString() : this.state.number1 + number.toString();
    } else {
      newNumber = (this.state.number2 === "0") ? number.toString() : this.state.number2 + number.toString();
    }

    // Change number
    this.setState({
      [changeNumber]: newNumber,
    });

  }

As you can see, I also check if the number is only a zero, and if it should be replaced by a number.

Next up, the click handlers for the operator buttons and AC. These are simply just setting the state of the App.


// Set the operator
  setOperator = operator => {
    this.setState({
      operator,
      isNumber1: false
    });
  }

  // AC Button - All Clear
  allClear = () => {
    this.setState({
      isNumber1: true,
      number1: "0",
      operator: "",
      number2: "0"
    });
  }

For adding the ability to add a decimal I can mainly do the same thing I did for adding a number, just add a “.” behind the number. I also make sure to check if there’s any periods in the number beforehand, and then in-case exiting the function.


  // Adds a "." to the number
  addDecimal = number => {

    // Decide which number to change
    let changeNumber = this.state.isNumber1 ? "number1" : "number2";

    // If the number already contains a '.' then just exit the function
    if (this.state.isNumber1) {
      if (this.state.number1.includes('.')) return;
    } else {
      if (this.state.number2.includes('.')) return;
    }

    // Get the new number to set
    let newNumber = this.state.isNumber1 ? this.state.number1 + "." : this.state.number2 + ".";

    // Set the number
    this.setState({
      [changeNumber]: newNumber
    });
  }

To make the Backspace click handler, I use substring (remember, I treat numbers as string) to get a new string without it last’s char.


  removeLast = () => {
    let number = this.state.isNumber1 ? this.state.number1 : this.state.number2;
    let changeNumber = this.state.isNumber1 ? "number1" : "number2";

    //Make a new number without the last char/number
    number = number.substring(0, number.length - 1);

    //Update the state
    this.setState({
      [changeNumber]: number
    });

  }

The equal button click handler is quite simple:


  //Solves the equation
  equals = () => {
    let operator = this.state.operator;

    let number1 = Number(this.state.number1);
    let number2 = Number(this.state.number2);
    let answer = 0;

    if (operator === "+") {
      answer = number1 + number2;
    } else if (operator === "-") {
      answer = number1 - number2;
    } else if (operator === "x") {
      answer = number1 * number2;
    } else if (operator === "/") {
      answer = number1 / number2;
    }

    this.setState({
      isNumber1: true,
      number1: answer.toString(),
      operator: "",
      number2: "0"
    });

  }

All the click handler’s are done. Now we need a number screen to show what’s happening. For this, I made a “NumberScreen” component which takes in the two numbers and the operator as a prop, and show’s it in the render function.


class NumberScreen extends Component {
    render() {
        //Destructering
        const { number1, operator, number2 } = this.props;

        //if number2 doesn't contain anything other than zero, don't show it
        let displayValue = (number2 === "0") ? number1 + operator : number1 + operator + number2;

        return (
            <div>
                <input className="number-screen" disabled value={displayValue} />
            </div>
        )
    }
}

Last, we just need to put the pieces together in our App’s render function:


render() {
    return (
      <div className="calculator">

        <p className="logo-text">My React Calculator</p>
        {/* Number Screen */}
        <NumberScreen number1={this.state.number1} operator={this.state.operator} number2={this.state.number2} />

        <div className="calculator-buttons">
          {/* Row 5 */}
          <Button buttonValue="AC" clickHandler={this.allClear} wide color="red" />
          <Button buttonValue="←" clickHandler={this.removeLast} color="red" />
          <Button buttonValue="/" clickHandler={this.setOperator} color="orange" />

          {/* Row 4 */}
          <Button buttonValue="7" clickHandler={this.addNumber} />
          <Button buttonValue="8" clickHandler={this.addNumber} />
          <Button buttonValue="9" clickHandler={this.addNumber} />
          <Button buttonValue="x" clickHandler={this.setOperator} color="orange" />

          {/* Row 3 */}
          <Button buttonValue="4" clickHandler={this.addNumber} />
          <Button buttonValue="5" clickHandler={this.addNumber} />
          <Button buttonValue="6" clickHandler={this.addNumber} />
          <Button buttonValue="-" clickHandler={this.setOperator} color="orange" />

          {/* Row 2 */}
          <Button buttonValue="1" clickHandler={this.addNumber} />
          <Button buttonValue="2" clickHandler={this.addNumber} />
          <Button buttonValue="3" clickHandler={this.addNumber} />
          <Button buttonValue="+" clickHandler={this.setOperator} color="orange" />

          {/* Row 1 */}
          <Button buttonValue="0" wide clickHandler={this.addNumber} />
          <Button buttonValue="," clickHandler={this.addDecimal} />
          <Button buttonValue="=" clickHandler={this.equals} color="blue" />

        </div>
      </div>
    );
  }

As I said earlier, it would be better to have the buttons in it’s own component for organizing and just better programming, but it works for this little project.

And that’s it! I’m really happy about how this project turned out, but I do have some regrets. The biggest one being how I’m deciding what number to add to. I think I could have come up with a better way to deal with that, but that will have to be for another time!

Thanks for reading!

GitHub Link



Copyright 2020. All rights reserved.

Posted July 16, 2020 by Atle in category "ReactJS

Leave a Reply

Your email address will not be published. Required fields are marked *