Avoid deriving state from props in React

A common footgun in React projects is derived state, which means basing state on props. This can be directly copying props into state, or deriving state from props with some extra logic.

This often causes trouble because once the state is set based on props, it won’t be updated by subsequent updates to props. This is after all the purpose of props and state in React: props are clean and stateless, and state is of course stateful.

To give a minimal example, here’s a parent component with a child component in JSX:

<Parent>
  <Child foobar={100}>
</Parent>

The parent is passing a value of 100 for the child’s foobar prop.

The child component implementation might look like this:

class Child extends React.Component {
  state = {
    derivedThing: this.props.foobar * 2
  }
}

The derivedThing state on Child is derived state as it’s derived from the foobar prop that is passed in.

This is likely to cause problems if the parent component does an initial render with a default value and then a subsequent render with a new value, for example after fetching some data from the server.

For example, if the parent is a functional component it might look like this:

(This has a bug due to derived state!)

const Parent = () => {
  const [foobar, setFoobar] = useState(0)

  useEffect(() => {
    fetch("example.com/foobar.json")
        .then(res => res.json())
        .then(json => setFoobar(json.foobar))
  , [])

  return (
    <div>
      <Child foobar={foobar}>
    </div>
  )
}

This will lead to a bug:

  1. The Parent initialises foobar to 0
  2. The Parent renders Child with the foobar prop as 0
  3. The Child initialises its derivedThing state to 0
  4. The Parent’s useEffect call fetches the real value for foobar, e.g. 100
  5. The Parent re-renders the Child with the foobar prop as 100
  6. The Child has already initialised its derived state, so it ignores the new prop value.
  7. The Child is now stuck with its derivedThing state as 0.

This is quite a common footgun in React, which is why it’s best to avoid derived state whenever possible.

It might help to remember the rule of thumb that state is for interactivity. In other words, state is not for general variables in the program, it’s for interactivity with a human user, with state that will change over the course of the React process lifetime due to user actions.


Tech mentioned