You Might Not Need an Effect - discussion (2022-08-06)
I keep seeing "don't use effects for sync" in reviews and I agree in theory, but the migration path is where people get stuck.
What are your go-to replacements for common sync effects (derived values, reset on prop change, fetch when X changes)? When do you keep an effect anyway because it's just simpler? Do you render signals for resets/derived computations so behavior is explainable from screenshots?
Comments (10)
Back to latestThe biggest win is deleting effects that compute derived state late. If it's derivable, derive it.
Resets are the key one: either do a keyed remount or make reset an explicit action, not timing.
If you hide reset in an effect, you get race conditions with back/forward.
Related: Core Concepts Best Practices and the routing angle in Navigation Routines (Deep Dive #4).
Keyed remount is blunt but deterministic:
tsx
function EditorRoute({ docId }: { docId: string }) {
return <Editor key={docId} docId={docId} />;
}
The nice part is you can write a signal for the boundary: resetSignal={reason:'docId-change'} and tests can assert it without timing.
Counterpoint: keyed remounts can wipe user state in surprising ways (scroll, focus, unsaved drafts).
We prefer explicit reset actions when users might reasonably expect continuity.
Agree. I treat remount as the hard boundary tool.
If users expect continuity, explicit reset actions + signals (and sometimes migration adapters) are safer.
Yep. The issue isn't remounts, it's using remounts as a hidden side effect.
For fetch-when-X-changes, we moved it into the render contract and rendered fetch signals. It removed the "why did it double fetch" class of issues.
We still keep some effects because they're pragmatic (analytics, localStorage writes).
The rule is they must be idempotent and must not correct render output after the fact.
The strongest heuristic: if the effect exists to make render correct, it's a bug. If it exists because the outside world needs a side effect, it's probably fine.
Signals are underrated here. A resetSignal turns "it cleared my input" into something you can reason about and test.
We also ban disabling the exhaustive-deps rule. If deps are annoying, it's usually telling you the design is unstable.
When you delete sync effects, you often end up with better URL contracts. It's a good forcing function.