Why my useEffect ran twice on mount
// CONTEXT
Building the dashboard on Lookout. On mount, the component fetched the current user. In the Network tab I saw two identical GET /me requests, every reload, both returning 200.
// WHAT I THOUGHT FIRST (and got wrong)
- Suspected my router was mounting the page twice. Added logs in the route config — nope, single mount. - Suspected a stale dependency array re-triggering the effect. But [] can't re-run on deps. Dead end. - The tell: both requests were identical and both succeeded. Nothing was failing. So the effect itself was being invoked twice, on purpose, by something.
// THE CODE THAT DID IT
useEffect(() => {
fetchUser().then(setUser); // no cleanup
}, []);// WHAT I FOUND
React 18 StrictMode mounts → unmounts → re-mounts every component in dev, specifically to expose effects that aren't safe to run twice. Mine had no cleanup, so React was flagging it. The fix isn't to disable StrictMode — it's to make the effect cancellable:
// THE FIX
useEffect(() => {
const controller = new AbortController();
fetchUser({ signal: controller.signal }).then(setUser);
return () => controller.abort();
}, []);// LIMIT
Production doesn't double-invoke, so this was never a user-facing bug. And if setUser fires after unmount on a slow response I'd still want a mounted-check — didn't need it here, noting it for later.
// TAKEAWAY
A double effect in dev isn't noise to silence, it's a missing cleanup to write.