Synchronizing with Effects - discussion (2022-11-26)
Re-reading the 'Synchronizing with Effects' article reminded me how often we reach for effects as a patch layer when the code could be modeled more directly.
What's your personal threshold for introducing an effect (DOM, subscriptions, timers) vs keeping things derived from render state? How do you keep cleanup disciplined so it doesn't turn into 'best effort'? Do you have conventions for making effect behavior visible when debugging (markers, named subscriptions, etc.)?
Comments (10)
Back to latestMy rule: if it doesn't touch something outside React (DOM, browser API, subscription), I'm suspicious.
If it's just fixing state after render, I'd rather refactor than add another effect.
Cleanup discipline improves a lot if you treat cleanup as part of the contract and not an afterthought.
When we had timing bugs, it was almost always 'cleanup doesn't match setup'.
Related: Performance and Rendering Best Practices.
We started naming effects in code review: 'subscribe:presence', 'timer:autosave', 'dom:focus'.
Just the naming step made people think twice about adding a new one.
Counterpoint: effects are fine, but dependency management is where projects go to die.
If you can't explain why an effect runs, you won't be able to debug it when it does.
Yep. The article's emphasis on thinking in terms of synchronization is useful here.
It gives you a sentence: 'this effect synchronizes X with Y'. If you can't write that, it's probably not a sync effect.
A practical trick: keep effect inputs tiny. Don't feed it a whole object, feed it the one primitive it actually syncs on.
Then the dependency story becomes obvious and cleanup is less fragile.
The article's examples are good reminders that effects are adapters, not calculators. If you do need an adapter, write it like one (setup + cleanup in the same breath):
ts
useEffect(() => {
const id = setInterval(() => setTick((t) => t + 1), 1000);
return () => clearInterval(id);
}, []);We also made dependency arrays less mystical by banning object literals in deps.
If a thing needs to be a dependency, it needs a stable identity (or it belongs in a document/store).
Counterpoint to the "effects are a patch layer" framing: sometimes the outside world is the product (presence, media, notifications).
The trick is making the effect behavior legible so it's not just timing soup.
Yeah, totally. I didn't mean "avoid effects", I meant "avoid effects as internal reconciliation".
When it's real synchronization, I like the idea from the article: name what you're syncing, and make the outcome visible.
We started rendering a tiny internal marker for effect-driven adapters (subscription count, last sync time).
It made it much easier to debug "why did this keep polling" kinds of issues from screenshots.
Related: Testing and Debugging.
A misunderstanding I see: people treat cleanup as optional if the effect seems small.
But cleanup is the thing that makes the effect a contract. Without it, you don't have a lifecycle, you have a leak.