useEffect Audit
Overview
Run a structured review of every useEffect and useLayoutEffect in scope. For each one, decide whether to:
- •keep it as an external synchronization process,
- •move logic to an event handler,
- •derive during render, or
- •split/extract logic (including
useEffectEventwhere appropriate).
Apply the smallest behavior-preserving refactor that removes unnecessary Effects and fixes dependency/lifecycle bugs.
Primary references:
- •https://react.dev/learn/synchronizing-with-effects
- •https://react.dev/learn/lifecycle-of-reactive-effects
- •https://react.dev/learn/separating-events-from-effects
- •https://react.dev/learn/removing-effect-dependencies
- •https://react.dev/reference/react/useEffectEvent
- •https://react.dev/learn/you-might-not-need-an-effect
Audit Workflow
- •Inventory Effects:
- •Scan for
useEffect(anduseLayoutEffect(. - •Group by component and by synchronization intent.
- •Scan for
- •Choose the right primitive first:
- •Event handler if logic is caused by a specific interaction.
- •Effect if logic is caused by rendering/visibility and synchronizes with an external system.
- •Render-time derivation if logic only computes UI data.
- •For each kept Effect, validate lifecycle semantics:
- •Treat it as an independent start/stop synchronization process.
- •Ensure cleanup fully undoes setup.
- •Ensure setup -> cleanup -> setup (development stress-test) is safe.
- •Split unrelated synchronization into separate Effects.
- •Fix dependencies by changing code, then dependencies:
- •Include every reactive value read by the Effect.
- •Never suppress
react-hooks/exhaustive-deps. - •If dependencies are undesirable, refactor:
- •Move event-specific logic to handlers.
- •Move non-reactive constants outside the component.
- •Move object/function creation inside the Effect or extract primitives.
- •Use state updater functions when reading state only to compute next state.
- •Extract non-reactive logic into
useEffectEventwhen needed.
- •Apply minimal refactors one Effect at a time:
- •Preserve behavior and user-visible semantics.
- •Remove redundant state and redundant re-renders.
- •Report decisions and verification notes using the output format below.
Decision Rules
- •Keep an Effect only for synchronization with systems outside React:
- •Browser/DOM APIs, subscriptions, sockets, timers, imperative widgets, network sync tied to visibility.
- •Remove an Effect when it only transforms data:
- •Derive directly during render; avoid mirrored state.
- •Remove an Effect when it only handles a user action:
- •Execute in the event handler that caused it.
- •Split Effects by purpose:
- •One synchronization process per Effect.
- •Treat dependency arrays as a description of code, not a scheduling knob:
- •If you want different dependencies, change the code first.
- •Use
useEffectEventonly for non-reactive logic that must read latest props/state without re-synchronizing:- •Do not use it as a shortcut to hide real dependencies.
- •Do not pass Effect Events to other components/hooks.
- •Do not include Effect Events in dependency arrays.
Output Format
For each audited effect, produce:
- •
file:lineand component name - •Current effect intent (one sentence)
- •Classification:
KeeporRemove - •Trigger type:
Rendering-driven syncorInteraction-driven event - •Refactor pattern applied (from
references/checklist.mdand/or React docs) - •Dependency rationale (reactive values read and why listed)
- •Code-level change summary
- •Verification notes (what to test)
Guardrails
- •Do not move logic out of Effects if it changes true external synchronization semantics.
- •Do not silence
react-hooks/exhaustive-depsto force desired timing. - •Do not use
useEffectEventto avoid legitimate dependencies. - •Do not use refs as a hack to prevent Effects from running in development.
- •Do not add
useMemo/useCallbackunless required for correctness or measured performance. - •Prefer the smallest safe refactor and verify with targeted tests, including remount/re-sync behavior.