No useEffect
Default stance: do not add raw useEffect or useLayoutEffect in application components until the cause is classified.
Use this skill when:
- a diff adds
useEffectoruseLayoutEffect - refactoring existing effects
- debugging stale state, extra renders, render loops, dependency churn, remount issues, or parent/child sync bugs
- deciding between render-time derivation, event handlers, data APIs,
useSyncExternalStore,key,useMountEffect, or a named hook
Fast triage
- Pure derivation from props/state? Compute during render. Use
useMemoonly for measured expensive work. - Specific user interaction? Put it in the event handler or action. Do not relay through state + effect.
- Screen-driven data loading? Use framework loaders, server components, or a query library. If the request is user-triggered, do it in the handler.
- Child effect pushing state or fetched data to a parent? Lift state or fetch in the parent instead.
- External store with subscribe + current snapshot semantics? Use
useSyncExternalStoreor the library’s hook. - Whole subtree should reset when identity changes? Remount with
key. - Only part of state adjusts on prop change? Derive it during render or redesign the state shape. Do not effect-reset it.
- Real external synchronization still remains because the component is on screen? Hide it in a descriptive custom hook. Use
useLayoutEffectonly for pre-paint layout/scroll work.
Red flags
useEffect(() => setX(f(y)), [y])- state flag -> effect -> reset flag
- child effect calling a parent setter
- fetch in effect followed by local
setState - chains of effects whose only job is to trigger more state
- empty
[]effect used as “runs once” - replacing
useEffectwithuseLayoutEffectto dodge the rule
Working mode
- Search for
useEffectanduseLayoutEffect. - Classify each with rules/01-triage.md.
- Rewrite with the smallest matching replacement from rules/02-replacements.md.
- If an effect survives, move it into a named hook and validate it with rules/03-allowed-effects.md.
- Run the checks in rules/04-enforcement.md.
Non-negotiables
- If the external system cannot be named explicitly, the effect is probably wrong.
useMountEffectis only a thin wrapper arounduseEffect([]). It does not mean “exactly once” in dev Strict Mode.- Raw effects in components are exceptional; surviving ones should usually live behind descriptive hooks.
useLayoutEffectis not a loophole. Keep it for pre-paint layout measurement and scroll correction only.
Additional resources
- rules/01-triage.md: first-match classification tree
- rules/02-replacements.md: concrete rewrites for the common anti-patterns
- rules/03-allowed-effects.md: the narrow set of surviving effects,
useMountEffect,useLayoutEffect, cleanup, Strict Mode, and SSR - rules/04-enforcement.md: lint and review enforcement
- examples/before-after.md: compact bad/good examples
- reference/react-rationale.md: the React model this skill is built on