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

You Might Not Need an Effect - discussion (2023-01-07)

Started by Theo Bennett
externalreact.deveffectsderived staterender boundaries

This article keeps coming up in code review, and I'm trying to separate "good effect usage" from effects that are really just papering over a missing render contract.

Read the article

When you delete "sync effects", what do you replace them with (derived keys, remount boundaries, explicit events)? Where does this advice break down for you (timers, subscriptions, analytics, legacy bridges)? Do you have a convention for making effect behavior visible so it isn't purely timing-based?

Comments (12)

Back to latest
Camille Duarte
Jan 10, 2023 · 04:18 PM

The biggest shift for us was turning "reset on prop change" into a boundary choice: remount or explicit reset action.

Once you stop trying to do it "after render", a bunch of bugs just disappear.

Marisol Vega
Jan 18, 2023 · 08:03 AM

Short take: if the effect exists to make render correct, it's already too late.

If it exists because the outside world needs a side effect, it's probably fine.

Dmitri Kline
Jan 26, 2023 · 09:55 PM

Keyed remount is still the most practical replacement for "sync draft with id":

tsx
function EditorRoute({ docId }: { docId: string }) {
  return <Editor key={docId} docId={docId} />;
}

function Editor({ docId }: { docId: string }) {
  const [draft, setDraft] = useState('');
  return <textarea value={draft} onChange={(e) => setDraft(e.target.value)} />;
}

It's blunt, but it's deterministic and testable.

Keira Santos
Feb 05, 2023 · 12:40 PM

For "fetch when X changes", we moved the fetch into the render contract and rendered a cache posture marker.

It makes the double-fetch conversations a lot less mystical.

Related posture: Data Fetching and Caching.

Lina Ortega
Feb 19, 2023 · 06:28 AM

Counterpoint: sometimes the effect is the simplest and I don't want the workaround to be *worse* than the timing bug.

But if we keep it, we force ourselves to render a tiny signal for it so at least support can see it.

Theo Bennett
Feb 22, 2023 · 05:10 PM

I'm with you. The article sometimes gets weaponized as "effects are forbidden".

For me it's more: effects are expensive *because they're invisible* unless you make them legible.

Nina Kapoor
Mar 03, 2023 · 09:12 AM

A convention we used: every effect has a name string, and the shell renders the current effect status for the route.

It's not pretty, but it stops "it only happens sometimes" from being un-debuggable.

Owen Price
Mar 11, 2023 · 03:55 PM

The advice breaks down when you're bridging legacy code. Sometimes you *need* an effect that writes state because the boundary is still moving.

But we treat those as migration-only and record them in the UI as "bridge lanes".

Jasper Nguyen
Mar 28, 2023 · 05:02 AM

I like the "explicit events" replacement: instead of "useEffect(() => setX(y))", you do "onUserAction => write both keys".

It reads more honest in code review.

Grace McCoy
Apr 12, 2023 · 06:44 PM

The real trap is when the effect becomes a second router: navigation happens, then the effect "corrects" state, then the UI flickers.

Once you spot that pattern, it's hard to unsee.

Salma Qureshi
Apr 28, 2023 · 02:20 AM

If you replace sync effects with derived state, store the derived key once and render it as evidence.

Otherwise people recompute slightly differently and you just moved the bug.

Benji Rios
May 19, 2023 · 10:33 AM

This thread made me realize our "effects discipline" rule is really "render discipline".

If behavior is invisible, it feels spooky no matter where it lives.

Amina Farouk
Jun 04, 2023 · 09:07 PM

Also: Strict Mode stress-testing is where the bad "sync" effects fall apart first.

If your app breaks when something runs twice, you probably encoded timing as correctness.