3

I've recently picked up react and I'm attempting to create a calculator as a leaning exercise for myself.

The aim is that whenever I click on a number button then that value will appear in the input component. So far I have got this to work but the value only changes on the second onClick event.

Currently the val1 prop disappears from the input on first click and then reappears on the second click with the previous button value.

Any tips on the structure of my code are more than welcome and any guidance going forward with the build of my calculator would also be great. I am still very new to this.

Here's my code below:

app.js

import React, {Component} from 'react';
import InputField from './Components/InputField';
import Numbers from './Components/Numbers';
import './App.css';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            number: 0,
            val1: 0,
            val2: 0
        }
    }
    numberChange(num1, num2){
        this.setState({val1: num1});
    }
    render() {
        return (
            <div className="App">
                <InputField val1={this.state.val1} value={this.state.number || ''} onChange={this.numberChange.bind(this)} />
                <Numbers onChange={this.numberChange.bind(this)} />
            </div>
        );
    }
}

export default App;

InputField.js

import React, {Component} from 'react';

class InputField extends Component {
    constructor(props) {
        super(props);
        this.state = {
            number: 0
        }
    }
    onChange(e) {
        console.log('onChange event fired');
        this.setState({
            number: e.target.value
        })
        this.props.onChange(this.state.number);
        console.log(this.state.number);
    }
    render() {
        return (
            <div className="inputField">
                <input type="number" value={this.props.val1 || ''} onChange={this.onChange.bind(this)}/>
            </div>
        );
    }
}

export default InputField;

Numbers.js

import React, {Component} from 'react';
import {Button} from 'react-bootstrap'

class Numbers extends Component {
    constructor(props) {
        super(props);
        this.state = {
            number: props.value
        }
    }
    number(e) {
        console.log('You\'ve pressed number ' + e.target.value);
        this.setState({
            number: e.target.value
        })
        this.props.onChange(this.state.number);
    }
    render() {
        return (
            <div className="buttons">
                <Button bsStyle="primary" value="1" onClick={this.number.bind(this)}>1</Button>
                <Button bsStyle="primary" value="2" onClick={this.number.bind(this)}>2</Button>
                <Button bsStyle="primary" value="3" onClick={this.number.bind(this)}>3</Button>
                <Button bsStyle="primary" value="4" onClick={this.number.bind(this)}>4</Button>
                <Button bsStyle="primary" value="5" onClick={this.number.bind(this)}>5</Button>
                <Button bsStyle="primary" value="6" onClick={this.number.bind(this)}>6</Button>
                <Button bsStyle="primary" value="7" onClick={this.number.bind(this)}>7</Button>
                <Button bsStyle="primary" value="8" onClick={this.number.bind(this)}>8</Button>
                <Button bsStyle="primary" value="9" onClick={this.number.bind(this)}>9</Button>
            </div>
        );
    }
}

export default Numbers;

Any help would be greatly appreciated!

AngryFridges
  • 413
  • 6
  • 13

2 Answers2

5

setState is an asynchronous function, meaning the data is only updated after you have called the onChange callback function. Therefore the App Component still gets the old value of this.state.number.

Try calling the onChange function after the setState is finished by:

number(e) {
    console.log('You\'ve pressed number ' + e.target.value);
    this.setState({
        number: e.target.value
    }, () => {
        this.props.onChange(this.state.number);
    })
}

More info here

Community
  • 1
  • 1
Kees van Lierop
  • 973
  • 6
  • 20
1

this.setState is async, then this.props.onChange(this.state.number); is called before the state really changed, and thus state.number has the old value,, you should do this: this.props.onChange(e.target.value);

 number(e) {
        console.log('You\'ve pressed number ' + e.target.value);
        this.setState({
            number: e.target.value
        })
        this.props.onChange(e.target.value); // check this
    }

and by the way you can add this to the constructor: this.number = this.number.bind(this) and as a result you can do this (notice that we just wrote :'this.number'):

  <Button bsStyle="primary" value="1" onClick={this.number}>1</Button>
  <Button bsStyle="primary" value="2" onClick={this.number}>2</Button>
  <Button bsStyle="primary" value="3" onClick={this.number}>3</Button>
  <Button bsStyle="primary" value="4" onClick={this.number}>4</Button>
  <Button bsStyle="primary" value="5" onClick={this.number}>5</Button>
  <Button bsStyle="primary" value="6" onClick={this.number}>6</Button>
  <Button bsStyle="primary" value="7" onClick={this.number}>7</Button>
  <Button bsStyle="primary" value="8" onClick={this.number}>8</Button>
  <Button bsStyle="primary" value="9" onClick={this.number}>9</Button>

even better you can do this instead of repeating your self 9 times:

render() {
        let buttons = []
        for(var i =1; i <10; i++)
           buttons.push(<Button bsStyle="primary" value={i} onClick={this.number}>{i}</Button>)
        return (
            <div className="buttons">
                {buttons}
            </div>
        );
    }
challenger
  • 2,184
  • 1
  • 18
  • 25