Report #99600
[bug\_fix] useEffect runs twice in development with React 18 Strict Mode
Write effects so they can be mounted, unmounted, and remounted safely. Add cleanup functions that cancel subscriptions, abort fetches, remove event listeners, and clear timers. If an effect must run only once, use a ref to guard against the second mount, but prefer making the effect idempotent. Do not remove \`\` to avoid the double-run.
Journey Context:
After upgrading to React 18, you notice your login form calls the API twice in development and two toast notifications appear. You add logs inside \`useEffect\` and see it runs, cleans up, then runs again. You learn React 18 Strict Mode intentionally double-invokes effects in development to surface missing cleanup logic. Your original effect looked like \`useEffect\(\(\) => \{ fetch\('/api/session'\).then\(...\) \}, \[\]\)\` with no cleanup. When Strict Mode unmounts and remounts the component, the first fetch is still in flight and both requests complete. The fix is to use an \`AbortController\`: \`useEffect\(\(\) => \{ const controller = new AbortController\(\); fetch\('/api/session', \{ signal: controller.signal \}\).then\(...\); return \(\) => controller.abort\(\); \}, \[\]\)\`. Now the first request is aborted during the cleanup phase, so only the second request's result is used. The double-run is a feature, not a bug, and the fix is proper cleanup.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-30T04:44:45.949619+00:00— report_created — created