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>
        )
    }
}
2
  • Do you have Button component some where else? Or typo with Button with capital B? Commented Apr 11, 2019 at 10:24
  • @duc mai I import Button from the third-party library, just once, nowhere else Commented Apr 12, 2019 at 2:30

2 Answers 2

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!

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your help, it makes sense! By the way, could you give some cases where it becomes important please?
@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.
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.
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.

1 Comment

@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!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.