跪拜 Guibai
← All articles
Frontend · JavaScript · React.js

Most of Your useEffects Shouldn't Exist

By kyriewen ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Unnecessary useEffects cause double-rendering, stale closures, and dependency-array bugs that waste debugging time. Recognizing that useEffect is an external-sync primitive — not a general-purpose state-change hook — eliminates a whole category of React performance and correctness problems.

Summary

A review of 37 useEffect hooks in a production React project revealed that 29 were redundant. The most common mistake is using useEffect to sync derived state — values that can be computed directly during rendering without an extra render cycle. Other frequent anti-patterns include routing event logic through useEffect instead of event handlers, hand-rolling data fetching instead of using React Query or SWR, and treating useEffect as a one-time initializer that breaks under React Strict Mode.

Each anti-pattern adds unnecessary renders, extra state variables, and dependency arrays that become bug vectors. The fix is usually shorter code: a derived constant replaces a state-plus-effect pair, an event handler absorbs logic that was artificially split across two locations, and a dedicated data-fetching library collapses 20 lines of manual fetch, cancel, loading, and error management into three.

The only legitimate use case for useEffect is synchronizing React with external systems — DOM observers, WebSocket connections, third-party chart libraries. If a useEffect body calls only React APIs like setState, it is almost certainly wrong.

Takeaways
Values computable from existing state or props should be derived during render, not stored in extra state synced via useEffect.
Logic triggered by a user action belongs in the event handler, not in a useEffect watching a flag state.
Hand-rolling data fetching in useEffect reinvents caching, deduplication, and race-condition handling that React Query and SWR provide out of the box.
Empty-dependency useEffects for one-time initialization break under React Strict Mode; module-level guards or useRef flags are safer.
useEffect is correctly used only for synchronizing with external systems — DOM APIs, WebSockets, browser APIs, and third-party libraries.
Deleting unnecessary useEffects shortens code, removes extra render cycles, and eliminates dependency-array maintenance.
Conclusions

The 80% redundancy rate found in the audit suggests that useEffect overuse is not a beginner mistake but a systemic pattern in how React developers are taught to think about state changes.

Treating useEffect as a general 'when X changes, do Y' mechanism conflates two distinct concerns — deriving values and synchronizing with external systems — that React already handles through separate, simpler mechanisms.

The quick-reference table effectively reframes useEffect from a Swiss Army knife into a specialized tool, which is a mental model shift that most React documentation fails to make explicit.

Concepts & terms
Derived state
A value that can be computed directly from existing state or props during rendering, without needing its own useState and useEffect. Examples include filtered lists, totals, and concatenated strings.
React Strict Mode double-invocation
In development, React Strict Mode intentionally invokes effect setup and cleanup twice to surface bugs. Empty-dependency useEffects that initialize SDKs or register global handlers will therefore run twice, causing duplicate side effects.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗