You Might Not Need an Effect - discussion (2022-11-12)
The “you might not need an effect” argument resonates, but in real apps the hard part is: what do you replace the effect with so the behavior is still explainable and reproducible?
When you delete effects, do you typically replace them with derived evidence stored in the route doc, or with explicit event handlers? What are your heuristics for spotting “sync effects” that should be contracts instead? Do you render any evidence markers so it’s clear *why* state looks the way it does after a refactor?
Comments (10)
Back to latestIf an effect exists to settle state, it’s probably hiding a missing contract decision.
Our most common replacement is “store derived evidence once”.
If multiple panels need the same derived thing, you don’t want them all re-deriving and disagreeing after render.
Related: Core Concepts Best Practices and State Management Best Practices.
A common “delete the effect” refactor for us is moving logic into an explicit handler + writing evidence:
tsx
const doc = useRouteDoc('search');
function onQuery(next: string) {
doc.write('query', next);
doc.write('queryEvidence', { at: Date.now(), reason: 'input' });
}
return <input onInput={(e) => onQuery(e.currentTarget.value)} />;Counterpoint: sometimes effects are the least bad tool for integrating with external APIs.
But I agree with the article that “sync internal state after render” is usually just a smell.
Yes. I’m mostly talking about the internal ones: “when A changes, set B” effects.
When we replace them with derived evidence, flicker goes away and tests become less racy.
Exactly. External sync: ok. Internal sync: contract it.
We deleted a bunch of effects and the unexpected result was: fewer “back button” bugs.
Turns out many of our effects were quietly rewriting state after navigation.
Heuristic I use: if an effect exists to keep two internal variables in sync, it’s wrong.
Either one is derived from the other, or you need a real event boundary where the change is decided.
Evidence marker helped us avoid regressions during effect deletions:
tsx
return <div data-evidence={JSON.stringify(doc.read('queryEvidence'))} />;
If the marker disappears or changes shape, tests catch it immediately.
This is also related to performance: “settle state after render” effects cause extra rerenders and often make typing feel worse.
Deleting them can be a perf win without touching micro-optimizations.
If you delete an effect and the UI becomes simpler to explain, you probably did the right thing.
The article is right, but the “replacement” is the hard part.
If you can’t explain the new contract from a screenshot, you’ll just re-add the effect later under pressure.