ADVANCED REACTJS • LESSON 27 OF 36

State Snapshots and Batching

Predict queued updates and avoid stale closures.

Read the explanation, run the worked code, then complete the practice lab in Assignments. This advanced lesson includes worked code and a dedicated practice lab.

Understand the idea

A render gives your component a snapshot of state. Its event handlers close over that snapshot; calling a setter queues work rather than rewriting the variable inside the running handler. Three calls to setCount(count + 1) in the same handler all request the same value. Three updater functions instead transform the queued value in sequence.

React groups many updates so a single interaction does not expose partially updated UI. Do not depend on an exact number of renders: Strict Mode, scheduling and bailouts can affect what you observe. Updater functions must be pure because React may call them more than once in development while checking for mistakes.

A timeout also captures the values from the render that created its callback. If delayed work must increment the latest value, use an updater. If it must remember what the user saw when scheduling, the captured value is appropriate. Decide the intended behavior instead of treating every closure as a bug. Derived information such as cart totals normally belongs in render, not in a second synchronized state variable.

Render snapshot ↓ Handler queues updates ↓ React processes queue ↓ Next render receives state
Trace the values through each step before changing the code.

Run the example

Use a separate React practice project with a Node.js version supported by Vite. Create it once with these commands, then use its development URL. Do not paste this into the Edupoly website’s own source files.

npm create vite@latest react-practice -- --template react
cd react-practice
npm install
npm run dev

Replace src/App.jsx with the following complete example. Keep Vite’s generated src/main.jsx entry point.

src/App.jsx

import { useState } from 'react';
export default function App() {
  const [count, setCount] = useState(0);
  return <main><h1>{count}</h1>
    <button onClick={() => {setCount(count + 1); setCount(count + 1); setCount(count + 1);}}>Three snapshot updates</button>
    <button onClick={() => {setCount(n => n + 1); setCount(n => n + 1); setCount(n => n + 1);}}>Three updater functions</button>
    <button onClick={() => setCount(0)}>Reset</button>
  </main>;
}

Expected result

From zero, Three snapshot updates produces 1. Reset, then Three updater functions produces 3.

Apply this to your assignments

Compare both buttons from zero and from ten. Add a delayed increment, click other buttons before it fires, and compare a captured value with a functional updater. Explain which behavior your cart quantity buttons require.

Practice starting point

Use the worked example above as your starting point.

Open Assignments for this lesson’s practice lab and acceptance checks.

Common mistakes

Never mutate a state object inside an updater. Do not await a setter expecting it to return the next state. Do not perform logging-to-server or other side effects inside an updater.

Check your understanding

  1. Predict the output before running the example, then compare it with the result panel.
  2. Explain which value each expression reads and which action changes it.
  3. Try the boundary cases listed in the practice task and record what you observe.
  4. Rebuild one matching assignment without copying, then compare behavior with the original example.

Reference

Official documentation for this topic · Vite setup requirements