Back to React
React
medium
mid

When should you avoid reaching for useEffect in React?

Don't use useEffect for: deriving state (compute during render); transforming data for rendering (compute or useMemo); responding to user events (do it in the handler); resetting state on prop change (use a `key`); chaining state updates (combine into one). Effects are for *synchronizing with external systems* — DOM, subscriptions, APIs — not for in-app data flow.

5 min read·~15 min to think through

useEffect is for synchronizing with external systems (DOM, subscriptions, network). Most "useEffect bugs" are actually misuses: code that should be in the render path, in a handler, or in a key — but ended up in an effect.

Misuse #1 — Deriving state from props

jsx
// BAD
const [fullName, setFullName] = useState("");
useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

This is double-rendering noise. Compute during render:

jsx
// GOOD
const fullName = `${firstName} ${lastName}`;

Or memoize if expensive:

js
const fullName = useMemo(() => expensive(firstName, lastName), [firstName, lastName]);

Misuse #2 — Filtering / transforming a list

jsx
// BAD
const [visible, setVisible] = useState([]);
useEffect(() => {
  setVisible(items.filter(matches(query)));
}, [items, query]);

Same problem. Compute during render or with useMemo:

jsx
const visible = useMemo(() => items.filter(matches(query)), [items, query]);

Misuse #3 — Responding to a user event

jsx
// BAD
useEffect(() => {
  if (justSubmitted) {
    api.send(formData);
    setJustSubmitted(false);
  }
}, [justSubmitted]);

Put the side effect in the event handler:

jsx
const onSubmit = () => api.send(formData);

Misuse #4 — Resetting state on prop change

jsx
// BAD
useEffect(() => {
  setSelected(null);
}, [userId]);

Use the key prop to remount and reset:

jsx
<UserPanel key={userId} userId={userId} />

Inside UserPanel, useState(null) re-initializes on key change. No effect needed.

Misuse #5 — Chaining state updates

jsx
// BAD
useEffect(() => { setB(deriveB(a)); }, [a]);
useEffect(() => { setC(deriveC(b)); }, [b]);

Each chain step is a render. Combine into one derivation:

jsx
const b = deriveB(a);
const c = deriveC(b);

If b/c aren't state (just derived), they don't need to be.

Misuse #6 — Initializing state from props

jsx
// BAD
const [value, setValue] = useState(0);
useEffect(() => { setValue(initial); }, []);

Just pass it to useState:

jsx
const [value, setValue] = useState(initial);

If you want the value to reset on prop change, use key (misuse #4).

Misuse #7 — Notifying the parent of a change

jsx
// BAD
useEffect(() => { onChange(value); }, [value]);

Call onChange directly when you update:

jsx
const handle = (v) => { setValue(v); onChange(v); };

Misuse #8 — Setting state during render (accidentally via effect)

jsx
// BAD
useEffect(() => { if (a > 100) setB(true); }, [a]);

If b is derived from a, just compute it:

jsx
const b = a > 100;

Misuse #9 — useEffect with a setTimeout/setInterval for animation

jsx
// BAD — frames lost, no rAF integration
useEffect(() => {
  const t = setInterval(() => setX(x + 1), 16);
  return () => clearInterval(t);
}, [x]);

Use requestAnimationFrame and refs (see [[build-a-cursor-tracker]]).

Legitimate uses

Effects ARE for:

  • Subscribing to non-React stores (Redux v8 useSyncExternalStore is the modern path).
  • Connecting to / disconnecting from sockets, timers, observers.
  • Reading/writing DOM that React doesn't manage (third-party libraries).
  • Fetching data — though React Query / Suspense are better for most cases.
  • Sending analytics / logging on view.

Mental rule

If the code reads or writes something outside React's tree, it might belong in an effect. If it transforms props into a value that the same component renders, it belongs in render.

Interview framing

"useEffect is for synchronizing with external systems — DOM, subscriptions, sockets, timers, third-party libraries. The common misuses: deriving state from props (compute during render or useMemo); filtering/transforming for render (same); responding to user events (do it in the handler); resetting state on prop change (use key to remount); chaining state updates (combine); initializing state from props (just pass to useState); notifying parents (call onChange directly). The rule: if you're transforming render-time data, it goes in render; if you're touching the outside world, it goes in an effect. Most 'useEffect bug' reports are actually 'this shouldn't be an effect at all'."

Follow-up questions

  • How do you reset state when a prop changes?
  • When is useMemo the right alternative to useEffect?
  • What does 'key' actually do here?
  • When IS useEffect the right tool?

Common mistakes

  • Deriving state in an effect → double-render.
  • Effects to call event-like side effects.
  • Effects to reset state instead of using key.
  • Chains of effects feeding each other.

Performance considerations

  • Removing useless effects removes double-renders. Memoize derivations only when measured to be expensive.

Edge cases

  • External libraries that demand an imperative effect dance — fine.
  • Effects in custom hooks that wrap third-party state — fine.
  • Suspense-based data fetching makes data-loading effects unnecessary.

Real-world examples

  • React docs 'You Might Not Need an Effect' page is canonical.
  • Dan Abramov's 'A Complete Guide to useEffect' on misuses.

Senior engineer discussion

Seniors compute during render where possible, use key for resets, put side effects in handlers, and reserve useEffect for external-system synchronization. They reach for useSyncExternalStore for external store integration.

Related questions