flushSync()
| Since: | React 18(2022) |
|---|
In React, flushSync() is a top-level function that forces state updates inside a callback to be reflected in the DOM immediately. Normally React batches multiple state updates together for rendering, but flushSync() forces synchronous rendering so that the latest DOM can be referenced right after the call.
Syntax
// Import from react-dom to use
import { flushSync } from 'react-dom';
// Force state updates inside the callback to be reflected in the DOM immediately
flushSync(() => {
// setState inside here is processed synchronously
setState(newValue);
});
// At this line, the DOM is already updated
Arguments
| Argument | Overview |
|---|---|
| callback | A callback function that takes no arguments. State updates called inside this function are processed synchronously and reflected in the DOM before the flushSync() call completes. |
Return Value
flushSync() returns undefined.
Sample Code
An example of scrolling to the newly added element immediately after adding an item to a list. Without flushSync(), state updates are batched, so the DOM may not be updated yet when scrollIntoView() is called.
import { useState, useRef } from 'react';
import { flushSync } from 'react-dom';
// Root component
export default function App() {
// Manage the list of items to display
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
// Ref to reference the last element in the list
const listEndRef = useRef(null);
function handleAddItem() {
// Use flushSync to reflect the state update in the DOM immediately
// This allows accessing the latest DOM on the next line
flushSync(() => {
setItems((prev) => [
...prev,
// Calculate the number of the item to add from the current count
'Item ' + (prev.length + 1),
]);
});
// After the flushSync call completes, the DOM is already updated
// So the newly added element exists and the scroll works correctly
if (listEndRef.current) {
listEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}
return (
<div style={{ fontFamily: 'sans-serif', padding: '16px' }}>
<h1>Item List</h1>
{/* Display the list in a vertically scrollable area */}
<ul
style={{
height: '150px',
overflowY: 'auto',
border: '1px solid #ccc',
padding: '8px 8px 8px 24px',
margin: '0 0 12px',
}}
>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
{/* Reference the empty element at the end with ref, used as scroll target */}
<li ref={listEndRef} style={{ listStyle: 'none', height: 0 }} />
</ul>
{/* Pressing the button adds an item and scrolls to the bottom */}
<button onClick={handleAddItem}>Add item and scroll</button>
</div>
);
}
Common Mistakes
Nesting flushSync calls
Calling flushSync() inside the callback of another flushSync() causes an error. Do not nest flushSync() calls.
ng_example.jsx
import { flushSync } from 'react-dom';
// NG: Nested flushSync calls
flushSync(() => {
flushSync(() => { // Error: flushSync cannot be nested
setState(newValue);
});
});
ok_example.jsx
import { flushSync } from 'react-dom';
// OK: Handle in a single flushSync without nesting
flushSync(() => {
setValueA(newA);
setValueB(newB); // Multiple state updates can be grouped in one callback
});
Infinite loop from using flushSync inside useEffect
Using flushSync() inside useEffect can cause an infinite loop, where flushSync() updates state synchronously, that update re-renders the component, and useEffect fires again.
ng_example.jsx
import { useState, useEffect } from 'react';
import { flushSync } from 'react-dom';
// NG: Using flushSync inside useEffect causes an infinite loop
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
flushSync(() => {
setCount((prev) => prev + 1); // Update → re-render → useEffect fires again → infinite loop
});
});
return <p>{count}</p>;
}
ok_example.jsx
import { useState, useEffect } from 'react';
// OK: Specify a dependency array in useEffect to prevent infinite loops
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount((prev) => prev + 1);
}, []); // [] means run only on the initial render
return <p>{count}</p>;
}
Performance degradation from unnecessary flushSync usage
flushSync() disables React's automatic batching (the optimization that groups multiple state updates into a single render). Overusing it causes synchronous rendering to run on every update, degrading performance. Limit the use of flushSync() to cases where immediate access to the DOM after an update is truly necessary, such as integrating with third-party libraries.
example.jsx
// Example of a case where usage should be limited
// (When a third-party library requires the latest DOM state)
import { flushSync } from 'react-dom';
function handleChange(value) {
flushSync(() => {
setValue(value); // Update DOM synchronously
});
// Third-party processing that assumes the DOM is already updated at this point
externalLibrary.refresh();
}
Overview
flushSync() is a top-level function imported from react-dom. It intentionally disables the automatic batching that became default in React 18 and forces synchronous rendering of state updates inside the callback. Because the DOM is in its latest state immediately after the flushSync() call completes, it is useful in cases where you need to immediately reference the post-update DOM size or position — such as scroll position adjustments, focus management, or integration with third-party libraries.
However, because flushSync() interferes with React's batching optimization, it affects performance. Using it excessively causes rendering to occur frequently and can actually degrade performance. When you need DOM access right after an update, there are also approaches to consider first: useLayoutEffect (for referencing the DOM right after rendering but before paint) and useRef.
Also, calling flushSync() inside another flushSync() callback should be avoided. Nesting can cause unexpected behavior. In contrast to flushSync(), when you want to lower the priority of state updates and defer rendering, there are approaches using startTransition or useDeferredValue.
Related pages: useLayoutEffect / startTransition / useRef
If you find any errors or copyright issues, please contact us.