Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. React Dictionary
  3. flushSync()

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

ArgumentOverview
callbackA 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 .