Why cleanup deserves its own checklist
Cleanup issues rarely make a component fail immediately. Instead they leave behind listeners, timers, subscriptions, and late async writes that accumulate until the product feels haunted.
Because the failure is delayed, teams often review cleanup casually. That is exactly why the mistakes survive into production.
What every effect should answer
Every effect should explain what it sets up, what can outlive the render safely, and what must be torn down when the dependencies change or the component unmounts.
- If the effect subscribes, where does unsubscribe happen?
- If the effect starts async work, what stops stale results from landing later?
- If the effect registers timers or DOM listeners, who removes them on dependency changes?
- If the effect has no external system to synchronize with, should it exist at all?
The cleanup cases teams under-test
Rapid remounts, tab switches, route changes, and repeated open-close interactions are where cleanup mistakes usually surface. Those cases matter more than one perfect happy-path render.
AI-generated code is especially vulnerable here because it tends to include just enough cleanup to satisfy a compiler or linter, not enough to survive reuse pressure.
The most useful habit
Treat cleanup as part of the feature contract, not a polite implementation detail. If the component can start work, it must also explain how that work stops.
That one habit makes effect review sharper and prevents background complexity from quietly compounding over time.