Derive, don't sync

Compute values from props and state during render, not in an effect.

React docsreact.dev
References: You Might Not Need an Effect
Learn ReactRead docs ↗

From vercel-react-best-practices, rule rerender-derived-state-no-effect.

Full name: two renders or one

Type in either field; red bars are stale frames.

useState + useEffectBad
fullName state
first + ' ' + last
in sync
0 renders0 stale frames
useEffect(() => setFullName(…), [first, last])
Derived in renderGood
fullName const
first + ' ' + last
in sync
0 renders0 stale frames
const fullName = first + ' ' + last
const fullName = firstName + ' ' + lastName

Reset with a key, not effect

Switch recipient, then type right away.

Reset in an effectBad
fresh
0 renders last switch0 stale frames
useEffect(() => setDraft(''), [userId])
Reset with keyGood
0 renders last switch1 mounts
<NoteEditor key={userId} userId={userId} />
<NoteEditor key={userId} userId={userId} />

A copy drifts from its source

Add or remove people, then type in the filter.

Copied into stateBad
    0 rendersmatches source
    useEffect(() => setVisible(items.filter(match)), [query])
    Derived in renderGood
      0 rendersmatches source
      const visible = items.filter(match)
      const visible = useMemo(() => items.filter(match), [items, query])

      Why it matters, pros and cons

      Why it matters

      An effect's setState costs a second render and a stale frame, and can drift.

      Pros

      • One render per change, no stale frame.
      • One source of truth.
      • No effect or dependency array to break.

      Cons

      • Expensive derivations need useMemo, not state.
      • key discards the whole subtree's DOM and state.
      • A draft seeded once from a prop is real state.