┌─ LOG ENTRY ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
LOG_0012026.06.28
[react][debug]

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.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────