React Forum
Blog & Guide Responses
Jan 06, 2023 · 04:00 AM

React useEffect - discussion (2023-01-06)

Started by Camille Duarte
externalreact.devuseEffecteffectscleanupdependencies

The useEffect reference page reads simple, but the real question is always the same: what are you synchronizing, and is effect timing part of your app's contract?

Read the docs

How do you handle dependency arrays in a way that stays understandable as code changes? Do you have patterns for keeping effect work small (so effects don't become mini state machines)? What do you do when a feature needs 'after paint' behavior but you still want the UI to be predictable?

Comments (10)

Back to latest
Maya Henson
Jan 06, 2023 · 04:06 AM

If the effect depends on 9 things, it isn't a sync step anymore, it's business logic hiding in a hook.

I'd rather extract the logic and keep the effect as the thin boundary to the outside world.

Dmitri Kline
Jan 06, 2023 · 04:14 AM

We made a rule that effects must have a cleanup path, even if it's a no-op at first. It keeps people honest.

Related: Core Concepts Best Practices.

Keira Santos
Jan 06, 2023 · 04:22 AM

Counterpoint: sometimes you *want* a mini state machine. The trick is making it explicit and not smuggling it into an effect.

If the machine exists, name the states and render a small marker in dev builds.

Avery Chen
Jan 06, 2023 · 04:33 AM

For dependency arrays, we try to make dependencies stable (callbacks, stable ids) rather than suppressing warnings.

If dependencies are unstable, the effect is telling you the surface isn't stable either.

Salma Qureshi
Jan 06, 2023 · 04:46 AM

One pattern that kept our effects thin: treat them as adapters and keep the business logic in pure functions.

ts
function computeNextStatus(input: any) {
  return { ready: Boolean(input), at: Date.now() };
}

useEffect(() => {
  const next = computeNextStatus(input);
  setStatus(next);
  return () => void 0;
}, [input]);
Hana Saito
Jan 06, 2023 · 04:58 AM

Counterpoint to "never mini state machine": sometimes the outside world *is* a state machine (sockets, subscriptions).

The key is keeping the states visible instead of letting them be implied by effect timing.

Maya Henson
Jan 06, 2023 · 05:03 AM

Yeah. My issue isn't the machine, it's the smuggling.

If it exists, model it explicitly and render a marker in internal builds so you can debug it.

Noah Vance
Jan 06, 2023 · 05:16 AM

A tiny repro of "dependencies exploded" that we see a lot:

tsx
useEffect(() => {
  doThing({ a, b, c });
}, [{ a, b, c }]);

If your deps are object literals, you're basically telling React "run every time". Stabilize the shape or lift it into a document/route store.

Priya Liao
Jan 06, 2023 · 05:29 AM

Where effects stayed understandable for us: route boundaries.

If the effect is part of the navigation story, we put it in the shell and render evidence (lane, last-run) so it doesn't become invisible behavior.

Benji Rios
Jan 06, 2023 · 05:41 AM

The react.dev page is also a reminder that effects are synchronization, not computation.

If you're using an effect to "fix" state after render, you're probably fighting the model and creating phantom states.

Inez Park
Jan 06, 2023 · 05:55 AM

A misunderstanding I had early on: I thought cleanup was only for subscriptions.

In practice, cleanup is also a declaration: "this effect has a lifecycle". Even a no-op cleanup makes you think about it as a contract.