In React 18, how many re-renders do two setState calls inside a setTimeout trigger, and why does your effect/console.log appear to run twice in development?
Two React 18 behaviors that surprise people: expanded batching and StrictMode's intentional double-invocation.
Batching: React groups multiple state updates into a single re-render for performance. Before React 18, this only happened inside React event handlers — updates in a setTimeout, promise, or native event handler each triggered their own render. React 18's *automatic batching* extends it everywhere, so two setState calls inside a setTimeout now cause one re-render, not two. (If you ever need to opt out and force a synchronous render between updates, ReactDOM.flushSync wraps the update.) StrictMode double-invocation: in development only (never production), <StrictMode> intentionally double-invokes component function bodies, initializers, and — since React 18 — mounts each component twice (mount, unmount, remount), running your effects setup→cleanup→setup. This is a deliberate stress test that surfaces bugs: impure render logic (a component that renders differently the second time has a side effect it shouldn't), and effects missing cleanup (a subscription that isn't torn down leaks on the simulated remount). The fix is never to defeat it — it's to make render pure and every effect's cleanup exactly reverse its setup. Seeing your log twice in dev is the signal working as intended; if that *causes* a bug (double API call that isn't idempotent), the effect needs cleanup or an abort, not the removal of StrictMode.
Reasoning about how many renders a batch of updates causes, or debugging dev-only double effects → automatic batching + StrictMode.