An interactive demo of the single most common useEffect mistake: using an effect to compute state from other state, instead of just computing it during render. Live render counters make the cost obvious.
▶ Live: https://derived-state.pages.dev/
const [first, setFirst] = useState("");
const [last, setLast] = useState("");
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(first + " " + last); // 🐞 mirror + sync
}, [first, last]);Every keystroke triggers two renders — one for the input change, then the effect runs and calls setFullName, forcing another. And for one frame fullName is stale (it still holds the previous render's value) until the effect catches up. In the demo, typing a few characters pushes this panel's render count to roughly double the other's.
const [first, setFirst] = useState("");
const [last, setLast] = useState("");
const fullName = first + " " + last; // ✅ just derive itOne render per keystroke, always correct, no duplicate state, no effect. If the computation is expensive, reach for useMemo(() => heavy(a, b), [a, b]) — still not an effect.
- Deriving data from props/state → compute during render (memoize if expensive).
- Responding to an event → do it in the event handler, not an effect.
- Resetting state when a prop changes → pass a
keyto remount, don't watch the prop. - Caching an expensive result →
useMemo, not state + effect.
Effects are for synchronizing with systems outside React: fetching data, subscriptions, timers, manually touching the DOM, sending analytics. The tell: if an effect's whole body is setState from other state/props, delete it — that value belongs in render.
This mirrors React's own You Might Not Need an Effect guidance; the demo just makes the render cost visible.
React 19 · TypeScript · Vite. StrictMode is off so the render counts are real, not the dev-mode double-invoke.
npm install
npm run devMIT © 2026 dev48v — dev48v.infy.uk