React

Derived state: stop copying props

Derived state is a value computed from props or from other state: copying it into useState creates a second source of truth that eventually diverges.

Key points

  • A value you can compute during render needs neither useState nor a synchronisation effect.
  • Copying props into state produces an extra render and one frame with the old value.
  • To start over when the entity changes, change the component key instead of adding an effect.

Derived state is a value computed from props or from other state. Copying it into useState creates a second source of truth, and that copy lags behind the moment the props change.

The symptom

The typical code is a useEffect that copies a prop into state whenever the prop changes. It works until the prop changes twice in the same commit, and it costs an extra render: the first shows the old value, the second shows the new one.

The fix

When the value can be computed during render, compute it: no effect, no extra render, no state to synchronise.

function SearchList({ items, query }) {
  const visible = items.filter((item) =>
    item.name.toLowerCase().includes(query.toLowerCase()),
  );

  return (
    
      {visible.map((item) => (
        {item.name}
      ))}
    
  );
}

When state is genuinely needed

  • User input: a field writes to state, it does not derive from a prop.
  • Reset per entity: when the prop identifies a different entity, change the component key and the state starts over with no effect.
  • Initial value: when only the first value of the prop matters, pass it to useState and never overwrite it afterwards.

The cost of the calculation

Filtering a thousand items on every keystroke is wasted work only when the render is frequent: measure first, then consider useMemo. Memoisation adds a dependency list to maintain, and getting it wrong produces the same defect as the copied state, with fewer clues.

Trying it with types

npm create vite@latest derived-demo -- --template react-ts
cd derived-demo
npm install
npx tsc --noEmit

Vite's react-ts template, under the MIT licence, includes React 19 and TypeScript; npx tsc --noEmit checks the types without writing files and flags a prop missing from the derivation.

Live records in the catalogue

Two records where derivation is the norm: filters, which filters a list from form values, and table, which sorts rows from current state. The rest sits among the 111,985 components and the 1,556 blocks.

The limitation

Computing during render works only when the calculation is pure and does not touch the DOM: a derivation that reads measurements or external data belongs in an effect. A heavy calculation repeated on every render is a performance problem, not an architecture one.

Where to continue

The order of calls is in rules of hooks; the stable function to pass to children is in useCallback; the comparison with a framework built on reactive state is in Svelte vs React.

Frequently asked questions

Are derived state and computed value the same thing?

Yes. Derived state is a value that depends on other sources and therefore needs no storage: it is recomputed during render whenever the sources change.

Can I sync a prop into useState in an effect?

Technically yes, but it is the pattern the React team advises against: it produces an extra render and can show a stale value. If the value is computable, compute it; if you need a reset, change the key.

How do I reset state when a prop changes?

With the key. Put key={entity.id} on the component that holds the state: React unmounts and remounts it when the id changes, and state restarts from the initial values with no effect.

Does useMemo replace derived state?

No, it optimises it. Derived state is the decision not to store the value; useMemo is an optional cache to avoid recomputing on every render. Remove the state first, add the cache later.

Is deriving on every render slow?

It depends on the calculation and the frequency. A filter over a hundred items is irrelevant; a sort over ten thousand rows on every keystroke gets measured in the Profiler before you add a cache.