React useEffect - discussion (2023-02-01)
Every time I re-read the useEffect reference I notice how much of the confusion comes from mixing responsibilities: derivation, orchestration, and reconciliation. In the React Tips-ish posture, effects are fine—great even—when they're named, bounded, and leave evidence (logs + UI attributes) so they aren't "invisible code".
What do you consider a legitimate useEffect in 2023: subscriptions only, or also orchestration and navigation normalization? How do you keep effects from accidentally becoming hidden state machines? Do you log effect behavior as readable contract lines (reason, lane, identity) or keep it silent unless it errors?
Comments (12)
Back to latestLegitimate effect for me = it touches the outside world (subscriptions, measurements, history, timers).
Everything else I try to treat as derived or event-driven so it's not timing-dependent.
We keep effects, but we insist they emit an explainable artifact (log line + evidence string). If an effect can't explain itself, it's probably doing too much.
Example contract log (the format is the point, not the exact fields):
txt
[tips] effect=subscribe:presence lane=pending reason=mount identity=user:42
[tips] effect=subscribe:presence lane=ok reason=socket:open identity=user:42
[tips] effect=subscribe:presence lane=cleanup reason=unmount identity=user:42
When someone reports "presence feels flaky", we can read the story instead of guessing.
Counterpoint: logging every effect creates log fatigue and people stop reading.
We log only contract transitions (subscribe/unsubscribe, identity changes, lane changes), not every re-render trigger.
Agreed. The log should read like a changelog, not like a heartbeat monitor.
If it isn't readable, it's not evidence—it's noise.
The hidden state machine part is real: effects tend to "remember" previous values implicitly via closures.
We keep a simple rule: if the effect depends on identity, it must include identity in its log/evidence and include it in its cleanup story.
We use effects for navigation normalization (e.g. missing defaults) but we treat it as orchestration and we name it as such. If the effect mutates history, we log the reason and the before/after URL so it doesn't feel like the URL changed "by itself".
txt
[tips] effect=nav:normalize action=replaceState from=/search?q=react to=/search?q=react&sort=recent reason=missingDefaultsThe reference doc made me more careful about dependency arrays as *contracts*.
If your effect has a dependency that isn't stable, you're signing up for a different lifecycle than you think.
One pattern I like: effects write into a lane, not directly into random state. So you can render data-effect-lane and tie it to user experience:
txt
[tips] effect=measure:layout lane=pending reason=resize
[tips] effect=measure:layout lane=ok reason=resize width=1024 height=768Counterpoint: some teams try to avoid effects entirely and end up doing worse things (work in render, random timeouts).
I'd rather have an effect that's documented and observable than a bunch of implicit timing hacks.
The part that bites us most is cleanup ordering during rapid identity flips.
We solved it by logging cleanup with the identity the subscription belonged to, and by treating stale cleanups as normal rather than as errors.
Docs tie-in: this is exactly why I like the repo's emphasis on evidence keys.
Effects are invisible unless you leave breadcrumbs for humans.
Related: API Reference and Testing and Debugging.
I still think a lot of "effect problems" are really "model problems".
If the state contract is unclear, the effect ends up compensating and becomes a hidden coordinator.
A good effect is a small, explicit bridge between worlds.
If it starts deciding product behavior (not just executing it), it probably needs to be promoted into a real contract with logs, evidence, and tests.