The essay focuses on the chat I have seen in some colour code often use componentWillReceiveProps the life cycle.

1. com ponentWillReceiveProps myth

We usually think componentWillReceiveProps only when there is a change from the parent component of props, so usually we may be in componentWillReceiveProps for below two kinds of operation.

Operation 1: Update the state of the child component by receiving props from the parent component.

Operation 2: Perform some side effects, such as sending a request.


2. Update the state using props

Conclusion: Not a good solution and may cause bugs.

2.1 What problems will it cause?

ComponentWillReceiveProps will render every time lead to child components in the parent component re – render time, even if the props didn’t change can also lead to this method is carried out. See this example where the parent component’s state changes causing the child component to re-render, even though the props passed to the EmailInput component does not change. But EmailInput components in componentWillReceiveProps unconditional implementation from props to update to the state program bugs.


Another common practice is to determine whether the current props and nextProps are equal, and then decide whether to update the props to state, as in the following example:


class EmailInput extends Component { state = { email: this.props.email }; ComponentWillReceiveProps (nextProps) {/ / as long as props. Email change, change the stateif (nextProps.email !== this.props.email) {
      this.setState({
        email: nextProps.email
      });
    }
  }Copy the code


This is also the most common way to write it, but when this happens, the expected click on the reset button does not reset the email value entered in our input box. The reason for this is that the props have not changed at this time, and therefore the passed props are not updated to the state of the component.


class EmailInput extends React.Component {
  state = {
    email: this.props.email
  };

  render() {
    return<input onChange={this.handleChange} value={this.state.email} />; } handleChange = event => { this.setState({ email: event.target.value }); }; ComponentWillReceiveProps (nextProps) {/ / props update state changesif(nextProps.email ! == this.props.email) { this.setState({ email: nextProps.email }); } } } class App extends React.Component {constructor() {
    super();
    this.state = { email: "[email protected]" };
  }

  handleReset = () => {
    this.setState({
      email: "[email protected]"
    });
  };

  render() {
    return( <Fragment> <EmailInput email={this.state.email} /> <button onClick={this.handleReset}>reset</button> </Fragment> ); }}Copy the code


Through the above two examples proved that using componentWillReceiveProps from props to update the state is not a good solution, we will update from props state called derived state (derived state). In the React 16, in view of the problem derived state, the React getDerivedStateFromProps has launched a new life cycle, replaces the componentWillReceiveProps, But this life cycle also has the above problems, as in the following example.

Using getDerivedStateFromProps


Class EmailInput extends React.Component {
    state = {
        email: this.props.email
    }
    static getDerivedStateFromProps (props, state) {
        if(props.email ! == state.email) {// props overwrites state's email each timereturn {
                email: props.email
            }
        }
        return null
    }
    render() {... }}Copy the code


Whatever state changes will become the incoming email, even though the parent component has not rerendered the state as in the previous example. The reason is that different from componentWillReceiveProps only at each parent component render lead to child components re – render time trigger, The getDerivedStateFromProps lifecycle is also triggered each time the component setState, as shown in the figure below. Either phase of Mounting or Updating the component triggers this lifecycle.





GetDerivedStateFromProps is a static method, which means it can’t get the instance’s this, so we want to check whether the props are updated before setState. There is no longer any way to use this.props. XXX. To fix this, we have to use the prevPropColor variable again:


Class EmailInput extends React.Component {
    state = {
        email: this.props.email,
        prevPropsEmail: ' '
    }
    static getDerivedStateFromProps (props, state) {
        if(props.email ! == state.prevPropsEmail) {return {
                email: props.email,
                prevPropsEmail: props.email
            }
        }
        return null
    }
    ... 
}Copy the code


And the React team doesn’t want to use the preProps variable to do this just yet. Imagine what the code would look like if the parent component passed enough props to update state each time one of them changed. It could be as messy and messy as this.


Class EmailNameInput extends React.Component {
    state = {
        email: this.props.email,
        name: this.props.name,
        prevPropsEmail: this.props.email,
        prevPropsName: this.props.name
    }
    static getDerivedStateFromProps (props, state) {
        let hasChange = false;
        const nextUpdateState = {};
        if(props.email ! == state.prevPropsEmail) { nextUpdateState.email = props.email; nextUpdateState.prevPropsEmail = props.email; hasChange =true;
        }
        if(props.name ! == state.prevPropsName) { nextUpdateState.name = props.name; nextUpdateState.prevPropName = props.name; hasChange =true;
        }
        returnhasChange ? nextUpdateState : null; }... }Copy the code


2.2 How to Solve this problem?

Derived State comes from two sources of data, one from the component’s own state change and the other from data changes in props.


Thinking about why we need to use Derived State comes down to the following situations (known as anti-patterns)

  • We need to update the props to state unconditionally
  • We need to update the props to state after checking whether the conditions are changed


For two kinds of inverse model, the above content has been introduced, whether is it is abandoned componentWillReceiveProps getDerivedStateFromProps all of these problems. The best solution is to turn the data into a single source.

Single data source principle

The single-source principle is that a component has only one source of data for its props or state. If a component has only the data passed to it by its props, the component is considered controlled (controlled by the parent component). If a component has only its own state data, The component is considered uncontrolled (unable to be controlled by the parent component). Components can avoid the above antipatterns as long as they conform to the single-data source principle.

1. Controlled components

Change the EmailInput component so that the component only has props, and all operations are done by the parent. In this case, all actions of the component are controlled by the parent, and the component is more like a presentation component.


const EmailInput = (props) => (<input onChange={props.onChange} value={props.email} />);Copy the code


2. Uncontrolled components

React doesn’t update the EmailInput component when the key changes. Instead, it creates a new component. Again, use the unique ID as the component’s key. When the Reset button is clicked, the new key is updated to generate a new component, and the new EmailInput component is recreated with the new initial value. This approach may have some performance cost, but there is a heavy logic in updating the component tree, which is faster because the subcomponent diff is omitted.

<EmailInput key={this.state.id} defaultEmail={this.state.email} />Copy the code


3. Other programmes

In cases where this is not possible without a proper key or for some other reason, the next best thing is to use getDerivedStateFromProps to update all related props based on a unique attribute, such as id.


class EmailNameInput extends Component { state = { email: this.props.email, name: this.props.name, prevPropsUserID: this.props.userID }; Static getDerivedStateFromProps(props, state) {// Reset all attributes associated with userIDif(props.userID ! == state.prevPropsUserID) {return {
        prevPropsUserID: props.userID,
        email: props.email,
        name: props.name
      };
    }
    returnnull; }}Copy the code


3. Perform side effects

Verdict: Don’t do it, the future is hard to maintain. As unsafe life cycle, in the forthcoming componentWillReceiveProps React 17 version is rejected.


3.1 Why was it abandoned?

The React team officially introduced Fiber in version 16.0.0 in 2017, but only an experimental release has been released to support enabling the core Concurrent Mode.




A brief description of the concept of Concurrent Mode:


Browser renderers are multithreaded, including the usual JS engine threads, GUI renderers, event triggers, timed triggers, and asynchronous request threads. To prevent the rendering process from having unexpected effects, the JS engine thread and the GUI thread are mutually exclusive, and the GUI rendering thread is suspended when JS executes. When React updates a component, it does a DOM Diff from the call lifecycle to the actual DOM. This is a synchronous operation, so when there are a lot of React components that need to be updated, the JS engine thread has to execute a long event. As a result, the browser does not immediately respond to user interface operations, such as user clicks, which need to be reported to the user in a timely manner.


React Fiber’s concurrency mode solves this problem by sharding the update process. After each shard is completed, it checks to see if there are any high-priority tasks that need to be processed first. If such tasks exist, updates will be stopped first and tasks with higher priorities will be executed to ensure smooth operation of users. But interrupted low-priority tasks need to be re-executed. This series of operations is accomplished by the Fiber Reconciler introduced by React16.


At the same time, the new Reconcile process is also divided into two stages. The first stage is the Render/Reconciliation stage, which can be interrupted. In this stage, the main task is to construct the workInProgress tree and find the DOM to be updated. The second stage is the COMMIT stage, which cannot be interrupted and is used to update the DOM collected in the first stage into the actual DOM node. React divides the life cycle into the following two phases: the first componentWillXXX (except componentWillUnmount).





Because the first stage can be interrupted, and low priority tasks and interrupt the need to perform again, so when a low priority task execution after componentWillReceiveProps lifecycle found themselves have run out of time and just at this time there is a high priority task need to be performed, Therefore, this low-priority task needs to be scrapped and re-executed. If at this time in componentWillReceiveProps life cycle made some asynchronous operations, such as sending a post request, may cause unexpected overwrite database, Therefore componentWillReceiveProps is there should be no side effects, in the same way it can effect to componentWillUpdate, componentWillMount the two life cycle, but this kind of thing all by itself, To avoid this, React simply decided to scrap the first phase of the componentWillXXX life cycle.



3.2 How to achieve this requirement?

ComponentDidUpdate is only executed once in the second phase. For example, componentDidUpdate needs to perform some operations (such as sending requests) after the props changes. It doesn’t cause unexpected problems. Also note that componentDidUpdate has setState behavior pay attention to the condition judgment so as not to cause an infinite loop.


/ / wrong componentWillReceiveProps (nextProps) {if(nextProps.id ! == this.props.id) { this.fetchAsyncData(nextProps.id); } // componentDidUpdate(prevProps) {if (this.props.id !== prevProps.id) {
        this.fetchAsyncData(this.props.id);
        }
}

Copy the code


This paper focus on some of the common pitfalls in componentWillReceiveProps lifecycle operations, just like the article, for to be scrapped componentWillUpdate, componentWillMount also exists some misunderstanding of the operation, These misoperations, which cannot be strongly controlled, sometimes lead to unexpected bugs.