Files
awesome-copilot/plugins/react18-upgrade/skills/react18-batching-patterns/references/flushSync-guide.md
2026-04-10 01:42:56 +00:00

2.4 KiB

flushSync Guide

Import

import { flushSync } from 'react-dom';
// NOT from 'react' - it lives in react-dom

If the file already imports from react-dom:

import ReactDOM from 'react-dom';
// Add named import:
import ReactDOM, { flushSync } from 'react-dom';

Syntax

flushSync(() => {
  this.setState({ ... });
});
// After this line, the re-render has completed synchronously

Multiple setState calls inside one flushSync batch together into ONE synchronous render:

flushSync(() => {
  this.setState({ step: 'loading' });
  this.setState({ progress: 0 });
  // These batch together → one render
});

When to Use

Use when the user must see a specific UI state BEFORE an async operation starts:

flushSync(() => this.setState({ loading: true }));
await expensiveAsyncOperation();

Use in multi-step progress flows where each step must visually complete before the next:

flushSync(() => this.setState({ status: 'validating' }));
await validate();
flushSync(() => this.setState({ status: 'processing' }));
await process();

Use in tests that must assert an intermediate UI state synchronously (avoid when possible - prefer waitFor).

When NOT to Use

Don't use it to "fix" a reading-this.state-after-await bug - that's Category A (refactor instead):

// WRONG - flushSync doesn't fix this
flushSync(() => this.setState({ loading: true }));
const data = await fetchData();
if (this.state.loading) { ... } // still a race condition

Don't use it for every setState to "be safe" - it defeats React 18 concurrent rendering:

// WRONG - excessive flushSync
async handleClick() {
  flushSync(() => this.setState({ clicked: true }));   // unnecessary
  flushSync(() => this.setState({ processing: true })); // unnecessary
  const result = await doWork();
  flushSync(() => this.setState({ result, done: true })); // unnecessary
}

Don't use it inside a useEffect or componentDidMount to trigger immediate state - it causes nested render cycles.

Performance Note

flushSync forces a synchronous render, which blocks the browser thread until the render completes. On slow devices or complex component trees, multiple flushSync calls in an async method will cause visible jank. Use sparingly.

If you find yourself adding more than 2 flushSync calls to a single method, reconsider whether the component's state model needs redesign.