Batch Updates and Batching — Complete Guide
In this tutorial, you will learn about Batch Updates and Batching. We cover key concepts, practical examples, and best practices to help you master this topic.
Batch updates group multiple state changes into a single DOM update cycle, reducing reflows and ensuring consistent UI state without intermediate flickering.
What You'll Learn
- What batching is and why frameworks use it
- How synchronous and asynchronous batching differ
- How React batches state updates
- How to implement your own batching system
Why It Matters
Without batching, multiple state changes trigger multiple re-renders and DOM updates, causing jank, flickering, and wasted computation. Batching is essential for smooth UI performance.
flowchart LR A[State change 1] --> B[Batching Queue] C[State change 2] --> B D[State change 3] --> B B --> E[Single reconciliation] E --> F[Single DOM update] F --> G[Single paint]
The Problem Batching Solves
Multiple state changes without batching cause cascading re-renders.
// Without batching: 3 separate re-renders
class UnbatchedComponent {
updateAll() {
this.setState({ a: 1 }); // Triggers re-render 1
this.setState({ b: 2 }); // Triggers re-render 2
this.setState({ c: 3 }); // Triggers re-render 3
// After 3 re-renders and 3 DOM updates, browser paints once
// But the component was built 3 times unnecessarily
}
}
// With batching: 1 re-render
class BatchedComponent {
updateAll() {
// All three setState calls are queued
this.setState({ a: 1 });
this.setState({ b: 2 });
this.setState({ c: 3 });
// At the end of the microtask, ONE re-render happens
// Component built once, DOM updated once, paint once
}
}
How React Batches Updates
React batches state updates in event handlers and lifecycle methods. Outside those, updates are not batched (before React 18).
import React, { useState } from 'react';
function BatchedExample() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
function handleClick() {
// Inside event handler: BATCHED (React 18+)
setCount(c => c + 1); // Queued
setCount(c => c + 1); // Queued
setCount(c => c + 1); // Queued
setText('updated'); // Queued
// All four updates are batched into ONE re-render
// count goes from 0 to 3 in a single render
}
function handleAsyncUpdate() {
// Before React 18, setTimeout did NOT batch
// In React 18+, setTimeout is also batched
setTimeout(() => {
setCount(c => c + 1);
setText('async update');
// React 18+: batched into one re-render
// React 17: two separate re-renders
}, 100);
}
return (
<button onClick={handleClick}>
{count} - {text}
</button>
);
}
Implementing a Simple Batching System
You can build your own batching mechanism using microtasks.
class BatchingFramework {
constructor() {
this._batchQueue = new Set();
this._isBatching = false;
this._pendingCallback = null;
}
// Called by components when their state changes
scheduleUpdate(component) {
this._batchQueue.add(component);
if (!this._isBatching) {
this._isBatching = true;
// Schedule batch processing at the end of current task
this._pendingCallback = Promise.resolve().then(() => {
this._flushBatch();
});
}
}
_flushBatch() {
// Process all queued updates
const components = Array.from(this._batchQueue);
this._batchQueue.clear();
this._isBatching = false;
// Build new virtual trees for all dirty components
const allPatches = [];
components.forEach(component => {
const newTree = component.render();
const oldTree = component._vdom;
const patches = this._diff(oldTree, newTree);
allPatches.push({ component, patches });
component._vdom = newTree;
});
// Apply all patches in a single batch
// The browser will see all DOM changes at once
allPatches.forEach(({ component, patches }) => {
this._commit(component.container, patches);
});
// After this microtask, the browser paints
// All DOM changes are visible in a single frame
}
_diff(oldTree, newTree) {
// Simplified implementation
if (!oldTree) return { type: 'CREATE', node: newTree };
if (!newTree) return { type: 'DELETE' };
if (oldTree.type !== newTree.type) return { type: 'REPLACE', node: newTree };
return { type: 'UPDATE', props: this._diffProps(oldTree.props, newTree.props), children: this._diffChildren(oldTree.children, newTree.children) };
}
_commit(container, patches) {
// Apply patches to the DOM
// All mutations happen synchronously here
}
}
Synchronous vs Asynchronous Batching
Different frameworks use different batching strategies.
// Synchronous batching (Vue 3):
// Updates are queued and flushed in the next microtask
// This ensures the DOM update happens asynchronously
// Vue uses a nextTick-based approach:
const queue = [];
let isFlushing = false;
function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job);
}
if (!isFlushing) {
isFlushing = true;
nextTick(flushJobs);
}
}
function flushJobs() {
// Sort jobs by component depth (parent before child)
queue.sort((a, b) => a.id - b.id);
const copy = queue.slice();
queue.length = 0;
for (const job of copy) {
job(); // Execute each job synchronously
}
isFlushing = false;
}
// React 18 uses automatic batching:
// All updates within the same event handler,
// timeout, promise, or native event are batched.
// This is always asynchronous (deferred to microtask).
flushSync — Opting Out of Batching
Sometimes you need synchronous DOM updates. React provides flushSync for this.
import { flushSync } from 'react-dom';
function SyncUpdates() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleSyncClick() {
// Force synchronous, unbatched update
flushSync(() => {
setCount(c => c + 1);
});
// DOM is updated here. count element shows new value.
// Browser may have already recalculated styles.
// This second update is also synchronous
flushSync(() => {
setFlag(f => !f);
});
// DOM is updated again. Two separate renders, two paints.
}
return <button onClick={handleSyncClick}>{count}</button>;
}
Common Mistakes
- Expecting all state updates to be batched automatically outside event handlers (pre-React 18).
- Reading DOM state between batched updates and getting stale values.
- Using flushSync excessively, negating the benefits of batching.
- Assuming batching guarantees a single paint (the browser may still coalesce multiple paints).
- Mutating state before batching completes, causing inconsistent UI state.
Practice Questions
- What is batching in Virtual Dom? Grouping multiple state changes into a single re-render and DOM update.
- How does React 18 handle batching? It batches all updates automatically, including setTimeout and promises.
- What is the benefit of batching? Fewer re-renders, fewer DOM operations, fewer paints, better performance.
- How do you opt out of batching in React? Use flushSync for synchronous, unbatched updates.
Challenge
Build a batching visualizer. Create a component with three independent counters. Add buttons that update all three at once with and without batching. Show a timeline of when each re-render and DOM update occurs. Measure and display the total time difference.
FAQ
Mini Project
Build a performance debugging tool that wraps a component and logs every state update, showing whether it was batched. Display a real-time counter of batched vs unbatched updates. Use it to profile a complex form component and identify unnecessary re-renders.
What's Next
Lesson 7: Fiber Architecture
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro