4

The Test component has a state of num, the component implements the function of clicking the button num+1, the button is bound to the self-increment method, the keyup is also bound to the method, the method uses the setState to re-render the value of num, but the effect of clicking the button with the mouse is not the same as the keyboard trigger event. Could you tell me why, please?

When I click the button, console logs the num first, then done.
But when I press enter, console logs done, then the num.

React15.5

class Test extends React.PureComponent {
    constructor(){
        super();
        this.state = {
            num : 1
        }
    }
    add = () => {
        const {num} = this.state;
        this.setState({num:num+1},()=>{
            console.log("done")
        })
        console.log(this.state.num)
    }

    componentDidMount() {
        document.body.addEventListener('keyup', this.add);
    }
    componentWillUnmount() {
        document.body.removeEventListener('keyup', this.add);
    }

    render() {
        return(
            <Button onClick={this.add} >add</Button>
            <span>{this.state.num}</span>
        )
    }
}
barbsan
  • 3,418
  • 11
  • 21
  • 28
Lancy
  • 43
  • 4

2 Answers2

1

Using the setState method with an object as the first parameter will execute the method asynchronously as descibed here. Therefore the order of console logs in your code may differ every time.

The reason you see a difference between the click and keyboard events is because the click event is a React.SyntheticEvent and the keyboard event is a DOM event. It seems like handling the click event takes less time so your console.log(this.state.num) executes before React is done updating your state.

If you want to see the same behaviour for both triggers I would suggest to use the componentDidUpdate lifecycle method. This lifecycle method waits until the update is done.

Edit

You can make the add method async and then await the execution of the setState method. Resulting add method:

add = async () => {
    await this.setState(
      {
        num: state.num + 1
      },
      () => {
        console.log("done");
      }
    );
    console.log(this.state.num);
};

This will make sure the code after await this.setState always waits untill the state has updated.

Ids van der Zee
  • 814
  • 1
  • 13
  • 22
  • @Ids van der Zee `React.SyntheticEvent` and DOM event you explain seems meaningful,but I wonder further reason. Async and await actually solve my problem.Thanks! – Lancy Apr 12 '19 at 03:16
1

I think @ids-van-der-zee's answer has some important points to consider. But I think the root cause for the difference in the console output is in this answer: https://stackoverflow.com/a/33613918/4114178

React batches state updates that occur in event handlers and lifecycle methods ... To be clear, this only works in React-controlled synthetic event handlers

I don't want to quote the entire answer, please read it, but in your case <Button onClick={this.add}... is a "React-controlled synthetic event handlers" where document.body.addEventListener('keyup', this.add); adds an event listener which is not part of the React infrastructure. So the Button onClick setState call is batched until the render completes (and your callback is not called until the batch is executed, where the keyup setState call is not batched and happens immediately--before the console.log(num) statement).

I don't think in your case this is of any consequence, but I think it shows great attention to detail that you noticed. There are cases where it become important, but I don't think you will hit them in this component. I hope this helps!

Henry Mueller
  • 1,309
  • 9
  • 8
  • Thanks for your help, it makes sense! By the way, could you give some cases where it becomes important please? – Lancy Apr 12 '19 at 06:12
  • @Lancy The default state mechanism in class React component--`setState({count: count+1},...)`--is designed in a way it won't come up often. You are pretty much going to make any state updates you need in a function with one call to `setState`, passing in all the new state values. I ran into this issue in code using the new Hooks API. The base function `setState` examples show things like `const [count, setCount] = useState(initialCount)` and tend to end up with many `setXX` functions. In non-batched functions (anything async), you will get a render for each one, `setCount`, `setMax`, etc. – Henry Mueller Apr 12 '19 at 23:24
  • Recently I plan to upgrade the 16.8 version of react and try to use Hooks, I hope to have a better coding experience. Thanks a lot. – Lancy Apr 15 '19 at 02:00