Lesson 8 / 25

Batching and Updater Functions

Why three increments can add one.

State is a snapshot per render

Inside one render, a state variable is a fixed snapshot. setA(a + 1) three times in the same handler uses the same a each time, so the result is +1. React also batches updates from the same event into one re-render. When the next value depends on the previous one, pass an updater function: setB(x => x + 1) queues updates that each receive the latest value, giving +3.

Snapshot versus updater functions, run

I ran this with React 19.3 and react-dom 19.3 on Node.js 22, compiling JSX with esbuild 0.28. Browser-style examples use jsdom 29 as a fake DOM (imported from a small setup-dom.mjs file) and act() to apply updates. One click calling setA(a + 1) three times leaves a = 1; one click calling setB(x => x + 1) three times gives b = 3. The two clicks together cause just 2 renders because updates in each handler are batched.

import "./setup-dom.mjs";   // test setup: jsdom provides a browser-like DOM in Node
import { useState, act } from "react";
import { createRoot } from "react-dom/client";

let renders = 0;
function Demo() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  renders++;
  return (
    <>
      <button id="stale" onClick={() => { setA(a + 1); setA(a + 1); setA(a + 1); }}>a = {a}</button>
      <button id="updater" onClick={() => { setB(x => x + 1); setB(x => x + 1); setB(x => x + 1); }}>b = {b}</button>
    </>
  );
}
const root = createRoot(document.getElementById("root"));
await act(() => root.render(<Demo />));
renders = 0;
await act(() => document.getElementById("stale").click());
await act(() => document.getElementById("updater").click());
console.log(document.getElementById("stale").textContent, "|", document.getElementById("updater").textContent);
console.log("renders for two clicks (three updates each):", renders);

Output:

a = 1 | b = 3
renders for two clicks (three updates each): 2

Use updaters for counters and toggles

Whenever the new state is computed from the old state, prefer the updater form.

Quick check: What does calling setCount(count + 1) three times in one click do?

  • Throws an error
  • Increases by 3
  • Increases count by 1, because count is the same snapshot each time
  • Resets count to 0
Answer

Increases count by 1, because count is the same snapshot each time — Use setCount(c => c + 1) for sequential updates.