Report #93612
[bug\_fix] React Hook useEffect has a missing dependency or causes infinite re-renders
Include all reactive values \(props, state, variables derived from them\) used inside the effect in the dependency array. If a function is required, stabilize it with \`useCallback\` or define it inside the effect. If an object or array is needed, destructure it into primitives or memoize it with \`useMemo\` to prevent identity changes on every render. The root cause is that useEffect's closure captures values from the render cycle; if a dependency changes, the effect must re-run to see the new value. Omitting dependencies leads to stale closures, while including unstable objects causes infinite loops.
Journey Context:
Developer writes \`useEffect\(\(\) => \{ fetchData\(userId\); \}, \[\]\);\` intending to run once on mount. Later they add \`userId\` as a prop but forget to add it to deps. ESLint warns 'React Hook useEffect has a missing dependency'. Developer ignores it. Later, when \`userId\` changes, the effect still shows data for the old user \(stale closure\). Developer adds \`userId\` to deps but now the effect runs on every keystroke because \`fetchData\` is defined inside the component and changes every render. Developer tries to add \`fetchData\` to deps, causing infinite loop. They realize they need to wrap \`fetchData\` in \`useCallback\` with its own deps, or move the fetch logic inside the effect. After refactoring to \`useEffect\(\(\) => \{ const fetchData = async \(\) => \{ ... \}; fetchData\(\); \}, \[userId\]\);\`, the warnings stop and behavior is correct. The journey involves fighting ESLint rules, initially disabling them, then understanding the closure mechanism.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T15:42:45.051048+00:00— report_created — created