patternjavascriptreactTip
flushSync forces a synchronous re-render for third-party DOM integration
Viewed 0 times
React 18+
flushSyncsynchronous renderbatchingDOM measurementthird party integrationscroll to bottom
browser
Problem
React 18 batches all state updates, including those inside setTimeout, promises, and event handlers. Sometimes you need a DOM update to be visible synchronously before continuing — for example, before calling a measurement API or syncing with a third-party library.
Solution
Wrap the state update in flushSync to force an immediate synchronous re-render:
import { flushSync } from 'react-dom';
function ScrollToBottom({ messages }) {
const bottomRef = useRef(null);
function handleSendMessage(text) {
// Force the new message to render immediately
flushSync(() => {
setMessages(prev => [...prev, { text, id: Date.now() }]);
});
// Now the DOM is updated — scroll to the new message
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
}
return (
<div>
{messages.map(m => <p key={m.id}>{m.text}</p>)}
<div ref={bottomRef} />
</div>
);
}
// Integration with non-React code
function handleThirdPartyEvent(data) {
flushSync(() => {
setData(data);
});
// thirdPartyLib reads the DOM after React has updated it
thirdPartyLib.measure(containerRef.current);
}
import { flushSync } from 'react-dom';
function ScrollToBottom({ messages }) {
const bottomRef = useRef(null);
function handleSendMessage(text) {
// Force the new message to render immediately
flushSync(() => {
setMessages(prev => [...prev, { text, id: Date.now() }]);
});
// Now the DOM is updated — scroll to the new message
bottomRef.current.scrollIntoView({ behavior: 'smooth' });
}
return (
<div>
{messages.map(m => <p key={m.id}>{m.text}</p>)}
<div ref={bottomRef} />
</div>
);
}
// Integration with non-React code
function handleThirdPartyEvent(data) {
flushSync(() => {
setData(data);
});
// thirdPartyLib reads the DOM after React has updated it
thirdPartyLib.measure(containerRef.current);
}
Why
React 18's automatic batching defers DOM updates for performance. flushSync opts out of batching for a specific update, committing it to the DOM synchronously. This is an escape hatch for cases where the timing of DOM updates is externally observable.
Gotchas
- flushSync is expensive — avoid it in render code or inside loops
- Calling flushSync inside a React lifecycle or effect can cause infinite loops
- It forces the batched updates inside it to flush, but may also flush other pending updates
- Only needed in rare integration scenarios — 99% of apps never need this
Code Snippets
flushSync then measure
import { flushSync } from 'react-dom';
// Force update then measure
flushSync(() => setItems(newItems));
const height = listRef.current.scrollHeight; // updated DOMContext
When you need a state update's DOM effects to be visible before proceeding with DOM measurement or third-party library calls
Revisions (0)
No revisions yet.