Skip to main content

React beginner exercise: Build a base arithmetic calculator

Exercise assignment:

Build a calculator with React which can Add, Subtract, Multiply and Divide.

  1. See image below for reference

  2. Give it whatever design and colors you want

  3. Each text field should have a placeholder

  4. The result field should be able to be copied but not edited

  5. Make sure your math is correct and calculator is well tested

  6. Post final Github solution and link to the forums: http://community.devslopes.com/category/33/react-flux-course

Example

  1. Remember the better you make this the better potential you have of using it in the future

Here's my uncompiled JSX and Sass-code:

// Main.jsx as starting point of the app.
var React = require('react');
var ReactDOM = require('react-dom');
var Calculator = require('./components/Calculator.jsx');

var NAV_BUTTONS = [
  { value: '+ Add',
    operation: 'add'
  },
  { value: '- Subtract',
    operation: 'subtract'
  },
  { value: 'x Multiply',
    operation: 'multiply'
  },
  { value: '/ Divide',
    operation: 'divide'
  }
];

ReactDOM.render(<Calculator navButtons={ NAV_BUTTONS } />, document.getElementById('app'));

// Calculator.jsx - Main app-component
var React = require('react');
var TextBox = require('./TextBox.jsx');
var Button = require('./Button.jsx');

var Calculator = React.createClass({
  INIT_STATE: { a: 0,
                b: 0,
                placeholderText: 'Enter a number ...',
                resultBox: '',
                aClass: 'input-box',
                bClass: 'input-box',
                aDisabled: false,
                bDisabled: false,
                buttonsDisabled: 'disabled' },
  operations: {
    'add': function() {
      return this.state.a + this.state.b;
    },
    'subtract': function() {
      return this.state.a - this.state.b;
    },
    'multiply': function() {
      return this.state.a * this.state.b;
    },
    'divide': function() {
      return this.state.a / this.state.b;
    }
  },
  getInitialState: function() {
    return this.INIT_STATE; 
  },
  updateNumbers: function(variable, reference) { 
    var val = parseFloat(reference.value);
    var varClass = [variable + 'Class'];
    
    if (typeof val === 'number' && !isNaN(val)) {
      if (this.state[variable + 'Class'].indexOf('invalid-input') > -1) {
        this.setState({
          [varClass]: 'input-box' 
        })
      }
      
      this.setState({
        [variable]: val,
        buttonsDisabled: ''
      });
    } else {
      this.setState({
        [varClass]: [varClass] + ' invalid-input',
        buttonsDisabled: 'disabled'
      });
    }
  },
  triggerOperation: function(operation) {
    var result = this.operations[operation].call(this);
    
    this.setState({
        aDisabled: 'disabled',
        bDisabled: 'disabled',
        buttonsDisabled: 'disabled'
      });

    this.refs.resultBox.refs.inputElement.value = result;
  },
  resetForm: function() {
    function resetElement(itemName, placeholder, disabled) {
      this.refs[itemName].refs.inputElement.value = ''; // Value must be empty f. placeholder to appear.
      this.refs[itemName].refs.inputElement.disabled = disabled;
      this.refs[itemName].refs.inputElement.placeholder = placeholder;
    }
    
    resetElement.call(this, 'a', this.INIT_STATE.placeholderText);
    resetElement.call(this, 'b', this.INIT_STATE.placeholderText);
    resetElement.call(this, 'resultBox', this.INIT_STATE.resultBox, 'disabled');
   
    this.setState({
      a: 0,
      b: 0,
      aClass: 'input-box',
      bClass: 'input-box',
      buttonsDisabled: 'disabled'
    });
  },
  render: function() {
    var that = this;
    
    var navButtons = this.props.navButtons.map(function(button) {
      return (
        <div>
          <Button value={ button.value } classDiv="large-3 medium-6 column"
                  classButton="calculation-method nav-button"
                  handler={ that.triggerOperation } operation={ button.operation } disabled={ that.state.buttonsDisabled }/>
        </div>
      );
    });
    
    return (
      <div className="calculator">
        
        <div className="row">
          <h1>Simple calculator</h1>
        </div>
        
        <div className="row">
          <TextBox divClass="large-6 columns"
                   placeholder={ this.state.placeholderText }
                   id="a" textBoxClass={ this.state.aClass }
                   ref="a"
                   value={ this.state.a }
                   changeHandler={ this.updateNumbers }
                   variable="a"
                   disabled={ this.state.aDisabled }
                   />
          <TextBox divClass="large-6 columns"
                   placeholder={ this.state.placeholderText }
                   id="b" textBoxClass={ this.state.bClass }
                   ref="b"
                   value={ this.state.b }
                   changeHandler={ this.updateNumbers }
                   variable="b"
                   disabled={ this.state.bDisabled }
                   />
        </div>
        
        <div className="row">
          { navButtons }
        </div>
        
        <div className="row">
          <TextBox divClass="medium-9 columns"
                   placeholder={ this.INIT_STATE.resultBox }
                   ref="resultBox" textBoxClass="input-box"
                   disabled="disabled" />
          <Button value="Clear" classDiv="medium-3 columns"
                  classButton="attention nav-button"
                  handler={ this.resetForm } />
        </div>     
      </div>
    );
  }
});

module.exports = Calculator;

// Button component 
var React = require('react');

var Button = React.createClass({
  render: function() {
    function notify(e) {
      this.props.handler(e.target.dataset.operation);
    }
    
    return (
      <div className={ this.props.classDiv }>
        <button href='#' className={ this.props.classButton } 
                    onClick={ notify.bind(this) }
                    data-operation={ this.props.operation }
                    disabled={ this.props.disabled } >
          { this.props.value } 
        </button>
      </div>
    );
  }
});

module.exports = Button;

// TextBox component
var React = require('react');

var TextBox = React.createClass({
  notify: function() {
    let item = this.refs.inputElement;

    this.props.changeHandler(item.dataset.variable, item);
  },
  render: function() {   
    return (
      <div className={ this.props.divClass }
           ref={ this.props.id }>
          <input type="text"
                 placeholder={ this.props.placeholder} 
                 ref="inputElement" 
                 className={ this.props.textBoxClass }
                 disabled={ this.props.disabled } 
                 onChange={ this.notify }
                 data-variable={ this.props.variable } 
                 />
      </div>
    );
  }
});

module.exports = TextBox;
$lightChange: 25%;
$borderRadius: 6px;

@mixin addPseudoClasses($selector, $color) {
  #{$selector}:visited, #{$selector}:hover {
    color: white;
  }

  #{$selector}:hover {
    background: linear-gradient(lighten($color, $lightChange), $color); 
    color: white;
    cursor: pointer;
  }

    #{$selector}:active {
    opacity: 0.6;
    box-shadow: 1px 1px 0 black;
  }
}

html, body {
    height: 100%;
}

body {
  background: linear-gradient(to top, #403B4A , #E7E9BB);
}

.nav-button {
  text-decoration: none;
  color: green;
  padding: 10px 20px;
  text-align: center;
  font-weight: 900;
  font-size: 1.2rem;
  margin-bottom: 16px;
  display: inline-block;
  width: 100%;
  border-radius: $borderRadius;
  letter-spacing: 1px;
  box-shadow: 2px 2px 0 black;
}

.nav-button[disabled] {
  color: crimson;
}

.calculation-method { 
  background: linear-gradient(to top, #abbaab ,#ffffff); 
}

@include addPseudoClasses('.calculation-method', #344334);

h1 {
  text-align: center;
  margin: 20px 0 30px;
  letter-spacing: 2px;
}

.attention {
  background: linear-gradient(to top, darken(#ED4264, $lightChange), #FFEDBC);
  text-transform: uppercase;
  color: white;
}

@include addPseudoClasses('.attention', red);

.invalid-input {
  border-color: red !important;
  background-color: pink !important;
}

input[type=text] {
  border-radius: $borderRadius !important;
  box-shadow: inset 1px 1px 0 black;
  padding-left: 20px;
  font-weight: 900;
}

The full project-code can be found on GitHub : https://github.com/mizech/simple-calculator

Here's a live demo: http://n-or.de/simple-calculator/

Have I fulfilled the assignment demands?

Moreover: The calculator works but I'm sure the programming could be improved.

Would appreciate to read your opinions and hints.

michael.zech
  • 4.5k
  • 8
  • 33
  • 56