Try @eslint-react/kit@beta
logoESLint React

no-flush-sync

Disallows 'flushSync'.

Full Name in eslint-plugin-react-dom

react-dom/no-flush-sync

Full Name in @eslint-react/eslint-plugin

@eslint-react/dom-no-flush-sync

Presets

dom recommended recommended-typescript recommended-type-checked strict strict-typescript strict-type-checked

Rule Details

flushSync can significantly hurt performance and may unexpectedly force pending Suspense boundaries to show their fallback state.

Most of the time, flushSync can be avoided, so use flushSync as a last resort.

Examples

Using flushSync to force synchronous updates

flushSync forces React to flush pending work synchronously, which can degrade performance and cause Suspense fallbacks to appear unexpectedly.

// Problem: flushSync hurts performance and can trigger unexpected fallback states.
import { flushSync } from "react-dom";

flushSync(() => {
  setSomething(123);
});

Using flushSync inside browser APIs that require synchronous DOM updates

Some browser APIs expect results inside of callbacks to be written to the DOM synchronously, by the end of the callback, so the browser can do something with the rendered DOM. In these cases, flushSync is the appropriate tool.

Due to current implementation limitations, this rule will still report an error for such legitimate uses. However, you can safely suppress it with // eslint-disable-next-line in these scenarios.

// OK: the beforeprint event needs the DOM updated before the print dialog opens
import { useState, useEffect } from "react";
import { flushSync } from "react-dom";

export default function PrintApp() {
  const [isPrinting, setIsPrinting] = useState(false);

  useEffect(() => {
    function handleBeforePrint() {
      // eslint-disable-next-line @eslint-react/dom-no-flush-sync
      flushSync(() => {
        setIsPrinting(true);
      });
    }

    function handleAfterPrint() {
      setIsPrinting(false);
    }

    window.addEventListener("beforeprint", handleBeforePrint);
    window.addEventListener("afterprint", handleAfterPrint);
    return () => {
      window.removeEventListener("beforeprint", handleBeforePrint);
      window.removeEventListener("afterprint", handleAfterPrint);
    };
  }, []);

  return (
    <>
      <h1>isPrinting: {isPrinting ? "yes" : "no"}</h1>
      <button onClick={() => window.print()}>
        Print
      </button>
    </>
  );
}

Using flushSync with startViewTransition

Similarly, flushSync can be appropriate when used inside startViewTransition (or a helper wrapping it) to ensure the DOM is updated synchronously before the browser captures the transition state.

The View Transitions API is still experimental and not fully supported in all browsers.

// OK: startViewTransition needs the DOM updated synchronously before capture
import { useState } from "react";
import { flushSync } from "react-dom";
import { transitionHelper } from "./utils";

export default function App() {
  const [count, setCount] = useState(0);

  const onIncrementClick = () => {
    transitionHelper({
      updateDOM() {
        // eslint-disable-next-line @eslint-react/dom-no-flush-sync
        flushSync(() => {
          setCount(count + 1);
        });
      }
    });
  };

  return (
    <div>
      <button onClick={onIncrementClick}>Increment</button>
      <div className="count">{count}</div>
    </div>
  );
}

Versions

Resources

Further Reading


See Also

On this page