React Ban useEffect
Treat direct effects as an external-synchronization escape hatch, not a default
state-management tool.
Workflow
- Search the touched React surface for direct imports, namespace calls, and
wrappers around
useEffect.
- Classify each effect before editing:
- render-time derivation
- server data or client-owned server state
- user-caused work
- identity-driven reset
- external store or system synchronization
- async UI or performance staging
- Apply the matching pattern in
replacements. Read
alternatives only when the effect exposes a
broader data, form, store, or performance ownership problem.
- Preserve the repository's framework, data layer, lint shape, and dependency
policy. Do not introduce a new library without approval.
- Run lint, type, and test gates plus the smallest real UI or hook check for
the changed behavior.
Decision Order
Prefer, in order:
- render-time calculation
- server, loader, or framework data API
- repository-owned server-state layer
- event handler, action, or mutation
- keyed component boundary
useSyncExternalStore
- reviewed domain-specific hook for a real external system
If none fits, explain the external synchronization contract before keeping an
effect.
Legitimate Effects
A reviewed integration hook should name the external system, own setup and
cleanup, expose its reactive inputs, and list every value used by setup or
cleanup. Prefer an existing repository hook. Do not accept a callback and
dependency array from callers or suppress exhaustive-dependency checks.
Never use an exception hook to fetch server state, copy props into state, relay
user actions, or hide a reset that belongs at a keyed boundary.
Enforcement
Extend the repository's existing linter to reject both named useEffect
imports and React.useEffect(...). Allow only narrow reviewed integration-hook
files. Keep the canonical local gate and CI on that same lint surface; optional
agent guidance or scanners do not replace mechanical enforcement.
In review, treat new direct effects as findings unless the change documents and
proves a legitimate external-system boundary. Ask for a replacement plan, not
dependency-array tuning.
Boundaries
- Scope migrations to the touched or requested surface.
- Leave
useLayoutEffect, framework lifecycle APIs, and non-React effect
systems alone unless requested.
- Apply performance primitives only for measured behavior or an established
repository pattern.
- Keep upstream attribution in upstream.
1---2name: react-ban-use-effect3description: Detect, replace, and mechanically restrict direct React `useEffect` calls. Use when writing, refactoring, reviewing, or migrating React code that fetches, derives state, relays events, resets state, or synchronizes through effects. Do not use for unrelated React work or non-React effect systems.4---56# React Ban useEffect78Treat direct effects as an external-synchronization escape hatch, not a default9state-management tool.1011## Workflow12131. Search the touched React surface for direct imports, namespace calls, and14 wrappers around `useEffect`.152. Classify each effect before editing:16 - render-time derivation17 - server data or client-owned server state18 - user-caused work19 - identity-driven reset20 - external store or system synchronization21 - async UI or performance staging223. Apply the matching pattern in23 [replacements](references/replacements.md). Read24 [alternatives](references/alternatives.md) only when the effect exposes a25 broader data, form, store, or performance ownership problem.264. Preserve the repository's framework, data layer, lint shape, and dependency27 policy. Do not introduce a new library without approval.285. Run lint, type, and test gates plus the smallest real UI or hook check for29 the changed behavior.3031## Decision Order3233Prefer, in order:34351. render-time calculation362. server, loader, or framework data API373. repository-owned server-state layer384. event handler, action, or mutation395. keyed component boundary406. `useSyncExternalStore`417. reviewed domain-specific hook for a real external system4243If none fits, explain the external synchronization contract before keeping an44effect.4546## Legitimate Effects4748A reviewed integration hook should name the external system, own setup and49cleanup, expose its reactive inputs, and list every value used by setup or50cleanup. Prefer an existing repository hook. Do not accept a callback and51dependency array from callers or suppress exhaustive-dependency checks.5253Never use an exception hook to fetch server state, copy props into state, relay54user actions, or hide a reset that belongs at a keyed boundary.5556## Enforcement5758Extend the repository's existing linter to reject both named `useEffect`59imports and `React.useEffect(...)`. Allow only narrow reviewed integration-hook60files. Keep the canonical local gate and CI on that same lint surface; optional61agent guidance or scanners do not replace mechanical enforcement.6263In review, treat new direct effects as findings unless the change documents and64proves a legitimate external-system boundary. Ask for a replacement plan, not65dependency-array tuning.6667## Boundaries6869- Scope migrations to the touched or requested surface.70- Leave `useLayoutEffect`, framework lifecycle APIs, and non-React effect71 systems alone unless requested.72- Apply performance primitives only for measured behavior or an established73 repository pattern.74- Keep upstream attribution in [upstream](references/upstream.md).