For a long time, componentWillReceiveProps life cycle is the premise of without additional render response props change and update the state of the only way. In version 16.3, we introduced a new alternative lifecycle getDerivedStateFromProps to solve the same problem more securely. At the same time, we realized that there were a lot of misconceptions about both approaches, and we found that some of the counterexamples triggered some strange bugs. In this release we fixed it and made The Derived State more predictable, so we can notice the results of abuse more easily.

This article contains both old componentWillReceiveProps converse also contains new getDerivedStateFromProps method

When to use Derived State

GetDerivedStateFromProps exists for one purpose only. It enables a component to update its own internal state in response to changes in props. Such as logging the current scrolling direction based on the changing offset attribute or loading additional data based on the source attribute we mentioned earlier.

We provide many examples because, in general, derived states should be used with caution. All of the derived state problems we’ve seen can be reduced to one of two things :(1) updating state unconditionally from props (2) updating state when the props and state don’t match (we’ll explore that in more detail below)

  • If you use derived state to remember the results of operations based on the current props, you don’t need it. See belowAbout Memoization.
  • If you are in the second case, your components may reset too often. Read on to learn more.

Common Bug with derived state

The terms “controlled” and “uncontrolled” usually refer to the input controls of a form, but they can also be used to describe where the component’s data resides. Data passed through props can be said to be controlled (because the parent component controls this data). Data with only internal state is said to be uncontrolled (because the parent cannot change it directly).

The most common mistake is to confuse the two. When a derived state is simultaneously updated by setState, the data loses a single source of fact. The loading data example mentioned above looks similar, but differs in some key ways. In this example, whenever the source property changes, the loading state must be overwritten. Conversely, the state is either overridden when the props changes, or managed by the component itself. (A single authentic source)

Problems occur when any of these restrictions are changed, and here are two typical examples.

Counterexample: Copy props to state unconditionally

Is a common misconception getDerivedStateFromProps and componentWillReceiveProps only props call “change”. These lifecyms are called when any parent component renders, whether or not the props actually changes. Therefore, it is not safe to use these cycles to override state unconditionally. Doing so causes state to lose updates.

Let’s demonstrate this problem. This is a mail input component that “maps” the email property to state:

class EmailInput extends 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) {/ / that erases all internal state will update! // Don't do this.setState({email: nextProps. Email}); }}Copy the code

It looks like this component is ok. State is initialized by the value in props and then updated with input to . But if the parent component render, whatever we typed in is gone! (See demo here) Even if we compare nextProps. Email before resetting! == this.state.email will do the same.

In this simple example, adding shouldComponentUpdate to restrict rerender only when the email in the props changes solves this problem. But in practice, components usually accept a lot of props. You can’t avoid other attributes changing. Functions and object attributes are often created inline, which makes it difficult to determine if something has changed substantially. Here’s a demo to illustrate it. Therefore shouldComponentUpdate is best used only to optimize performance, not to ensure the correctness of derived state.

Hopefully now we can figure out why copying props to state unconditionally is a bad idea. Before looking at possible solutions, let’s look at a related example: What if we update state only when the email property changes?

Counterexample: Override state when props changes

Continuing the example above, we can avoid accidentally overwriting existing state by updating state only when props. Email changes.

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

  componentWillReceiveProps(nextProps) {
    // Update state as soon as props. Email changes.
    if(nextProps.email ! = =this.props.email) {
      this.setState({
        email: nextProps.email }); }}// ...
}
Copy the code

Although the above example USES componentWillReceiveProps, but the same is also applicable to getDerivedStateFromProps the counterexample

We just took a big step forward. Now our component will only override what we typed when the props actually changed.

There is, of course, a delicate issue here. Imagine a password management app that uses the input component above. When switching between two different accounts, if the two accounts have the same email address, our reset will be invalid. Because the email attribute passed in for both accounts is the same. (If you switch one data source, but the emails in the two data sources are equal, the input field that should be reset is not reset.) Check the demo

The design is fundamentally flawed, but it’s an easy mistake to make. (I’ve done it myself.) Fortunately there are two better alternatives. The key is that with any data, you need to choose one component as its real source and avoid copying it in other components. Let’s look at the following solutions.

A better solution

Recommended: Fully controlled components

One solution to the above problem is to remove state from our component entirely. If email exists only as an attribute, we don’t need to worry about conflicts with state. We can even use EmailInput as a more lightweight function component:

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

This simplifies the implementation of our component, but if we still want to save a draft of the input value, we need the parent component to do this manually, see demo

Recommended: Completely uncontrolled components identified by keys

Another alternative is for our component to fully control the “draft” state of eAMil. Here, our component still accepts the properties in props for the initial value, but it ignores subsequent property changes.

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

  handleChange = event= > {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />; }}Copy the code

To reset input values when switching between different things (like the password manager mentioned above), we can use the React key property. When a key changes, React creates a new component instead of updating it. Key is usually used for dynamic lists, but it is also useful here. Here we recreate the email input component with the user ID when selecting a new user:

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

Whenever the ID changes, EmailInput is recreated and state is reset to the latest defaultEmail value. You don’t have to add a key to every input field. It would be more useful to add a key to the entire form. Every time the key changes, all components of the form are rebuilt and assigned clean initial values.

In most cases, this is the best way to reset state.

Recreating a component may sound slow, but it has very little impact on performance. If the component has a lot of logic on updates, using keys can be even faster because the differences in the subtree can be bypassed.

Alternative 1: Reset the uncontrolled component through the ID attribute

If the key cannot be used for some reason (such as expensive component initialization), a possible but cumbersome approach is to listen for “userID” changes at getDerivedStateFromProps.

class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if(props.userID ! == state.prevPropsUserID) {return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
Copy the code

This also provides the flexibility to reset only the internal state of the widget if we choose to do so (see demo here)

The above example for componentWillReceiveProps is the same

Alternative 2: Use instance methods to reset uncontrolled components

In rare cases, you may need to reset the state without a proper ID as the key. One way is to set the key to a random or increasing value and change it whenever you want to reset it. Another possible solution is to expose an instance method to change the internal state implicitly:

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

  resetEmailForNewUser(newEmail) {
    this.setState({ email: newEmail });
  }

  // ...
}
Copy the code

The parent form component can call this method through ref (click here to see the demo).

Ref is useful in situations like this, but we recommend that you use it as sparingly as possible. Even in this case, this approach is not ideal because it will render twice.

About Memoization

We’ve also seen derived states used to ensure that a heavily computed value in render is only recalculated when the input changes. This technique is called Memoization.

Using it for caching memory is not necessarily bad, but it is usually not the best solution. Managing derived state has a level of complexity that increases with additional attributes. For example, if we add a second derived field to a component, we need to track the changes to those two fields separately.

Let’s take a look at an example where we pass in a list to this component, which then needs to filter out matching items based on the user’s input. We can use derived state to save the filtered list.

class Example extends Component {
  state = {
    filterText: ""};/ / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  // NOTE:This example is not what we recommend
  // See the following example for the recommended method.
  / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

  static getDerivedStateFromProps(props, state) {
    // Filters the list whenever the list array or keyword changes.
    // Notice that we need to store prevPropsList and prevFilterText to listen for changes.
    if( props.list ! == state.prevPropsList || state.prevFilterText ! == state.filterText ) {return {
        prevPropsList: props.list,
        prevFilterText: state.filterText,
        filteredList: props.list.filter(item= > item.text.includes(state.filterText))
      };
    }
    return null;
  }

  handleChange = event= > {
    this.setState({ filterText: event.target.value });
  };

  render() {
    return (
      <Fragment>
        <input onChange={this.handleChange} value={this.state.filterText} />
        <ul>{this.state.filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
      </Fragment>); }}Copy the code

The implementation here avoids unnecessary recalculation of the filteredList. But it’s too complicated, because it needs to track and listen for changes in props and state, respectively, to update the filter list properly. In this example, we can use PureComponent to simplify operations and put filters in the Render method:

// PureComponents will only render when at least one property in the props and state has changed.
// Changes are judged by reference comparisons.
class Example extends PureComponent {
  // State only needs to hold the current filter text value:
  state = {
    filterText: ""
  };

  handleChange = event= > {
    this.setState({ filterText: event.target.value });
  };

  render() {
    // this is called only if props. List or state.filterText changes.
    const filteredList = this.props.list.filter(
      item= > item.text.includes(this.state.filterText)
    )

    return (
      <Fragment>
        <input onChange={this.handleChange} value={this.state.filterText} />
        <ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
      </Fragment>); }}Copy the code

The above approach is simpler and cleaner than the derived version. This is not a good idea, however, because it can be slow for long lists, and PureComponent can’t prevent render from other property changes. In response to this situation we introduced an Memoization helper to avoid redundant filters.

import memoize from "memoize-one";

class Example extends Component {
  // State only needs to maintain the current filter keyword:
  state = { filterText: "" };

  // Refilter when the list array or keyword changes
  filter = memoize(
    (list, filterText) = > list.filter(item= > item.text.includes(filterText))
  );

  handleChange = event= > {
    this.setState({ filterText: event.target.value });
  };

  render() {
    // If the parameters have not changed from the last calculation when calculating the render list, 'memoize-one' will reuse the result returned last time
    const filteredList = this.filter(this.props.list, this.state.filterText);

    return (
      <Fragment>
        <input onChange={this.handleChange} value={this.state.filterText} />
        <ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
      </Fragment>); }}Copy the code

This makes it easier to perform the same functions as derived states!

conclusion

In practice, components usually contain both controlled and uncontrolled elements. That’s fine, if each data has a clear source, you can avoid the counterexample mentioned above.

The use of getDerivedStateFromProps is worth rethinking because it is an advanced feature of some complexity that should be used with caution.

You Probably Don’t Need Derived State