Created at

Adjusting some state when a prop changes

Sometimes, you might want to reset or adjust a part of the state on a prop change, but not all of it.

At first glance, you might think of using Effect like this:

function List({ items }) {
  const [isReverse, setIsReverse] = useState(false)
  const [selection, setSelection] = useState(null)

  // đź”´ Avoid: Adjusting state on prop change in an Effect
  useEffect(() => {
    setSelection(null)
  }, [items])
  // ...
}

But this is not ideal, it will cause an extra re-render of the List and its child components.

function List({ items }) {
  const [isReverse, setIsReverse] = useState(false)
  const [selection, setSelection] = useState(null)

  // Better: Adjust the state while rendering
  const [prevItems, setPrevItems] = useState(items)
  if (items !== prevItems) {
    setPrevItems(items)
    setSelection(null)
  }
  // ...
}

Storing information from previous renders like this can be hard to understand, but it’s better than updating the same state in an Effect. In the above example, setSelection is called directly during a render. React will re-render the List immediately after it exits with a return statement. React has not rendered the List children or updated the DOM yet, so this lets the List children skip rendering the stale selection value.

When you update a component during rendering, React throws away the returned JSX and immediately retries rendering. To avoid very slow cascading retries, React only lets you update the same component’s state during a render. If you update another component’s state during a render, you’ll see an error. A condition like items !== prevItems is necessary to avoid loops.

Although this pattern is more efficient than an Effect, most components shouldn’t need it either.

function List({ items }) {
  const [isReverse, setIsReverse] = useState(false)
  const [selectedId, setSelectedId] = useState(null)
  // âś… Best: Calculate everything during rendering
  const selection =
    items.find((item) => item.id === selectedId) ?? null
  // ...
}