简体   繁体   中英

How is React component's props updated asynchronously?

After digging around I thought I understood that a component's props cannot be updated by the component itself, although can be changed by the parent component.

However I found this one section in react docs:

React may batch multiple setState() calls into a single update for performance. Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

So I am trying to interpret what this excerpt says with I have understood. If props is updated, then the update must come from parent component. If the update comes from a parent component, why is it possibly asynchronous?

Please check the answer given by zloctb here

// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3

Solution
this.setState((prevState, props) => ({
  count: prevState.count + props.increment
}));

If props is updated, then the update must come from parent component.

That is correct.

If the update comes from a parent component, why is it possibly asynchronous?

You can pass parent's setState() with props to a child and call it somewhere in child's code. And it still may be asynchronous. I came up with the following example:

class Container extends React.Component {
  state = { containerState: 0 };
  render() {
    return (
      <Component
        containerState={this.state.containerState}
        setContainerState={this.setState.bind(this)}
      />
    );
  }
}

And then, in child Component , you have code like this:

this.props.setContainerState({
  containerState: this.props.containerState + 1
});

// You want to use updated this.props.containerState here, but you can't,
// because parent's setState() MAY BE deferred
this.props.setState({ componentStateUpdatedWithObject: this.props.containerState });

// Now if you use the function instead, you can expect to get updated props
// as a second argument
this.props.setState((state, props) => ({ 
  componentStateUpdatedWithFunction: props.containerState 
}));

See the full code for my example:

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> </head> <body> <div id="root"></div> </body> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin ></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin ></script> <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> <script type="text/babel"> class Container extends React.Component { state = { containerState: 0 }; render() { return ( <Component containerState={this.state.containerState} setContainerState={this.setState.bind(this)} /> ); } } class Component extends React.Component { state = { componentStateUpdatedWithObject: [0, this.props.containerState], componentStateUpdatedWithFunction: [0, this.props.containerState] }; log = title => { console.log(title); console.log("- containerState", this.props.containerState); console.log( "- componentStateUpdatedWithObject", this.state.componentStateUpdatedWithObject ); console.log( "- componentStateUpdatedWithFunction", this.state.componentStateUpdatedWithFunction ); console.log("=========="); }; update = () => { this.log("before update"); this.props.setContainerState({ containerState: this.props.containerState + 1 }); this.log("after setContainerState"); this.setState({ componentStateUpdatedWithObject: [ this.state.componentStateUpdatedWithObject[0] + 1, this.props.containerState ] }); this.log("after setState with object"); this.setState((state, props) => ({ componentStateUpdatedWithFunction: [ state.componentStateUpdatedWithFunction[0] + 1, props.containerState ] })); this.log("after setState with function"); }; componentDidMount() { // setInterval(this.update, 2000); } render() { this.log("on render"); console.log("---------------------------------------------"); return ( <div> <div>containerState: {this.props.containerState}</div> <div> componentStateUpdatedWithObject:{" "} {this.state.componentStateUpdatedWithObject.join(", ")} </div> <div> componentStateUpdatedWithFunction:{" "} {this.state.componentStateUpdatedWithFunction.join(", ")} </div> <button onClick={this.update}>UPDATE</button> </div> ); } } ReactDOM.render(<Container />, document.getElementById("root")); </script> </html> 

NOTE: Not all setState() calls are asynchronous. In my example if you uncomment setInterval(this.update, 2000) in the componentDidMount , you won't get the same behavior. This way setState() calls are synchronous.

See this link for explanation why this happens: When and why are setState() calls batched?

In short, this is because

Currently (React 16 and earlier), only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM