flushSync で state 更新を同期的にフラッシュする
2025-11-21
a month ago
開発環境
- react 19.2.0
- react-dom 19.2.0
前提
React の state 更新は通常「非同期的」にバッチングされます。
そのため、setState の直後に DOM 操作を行うと、反映前の DOM を扱ってしまうケースがあります。
こうした「DOM を即座に扱いたい場面」で利用できるのが flushSync。
flushSync を使うと、囲まれた state 更新が即時にフラッシュされ、同期的に DOM が更新されるようになります。
本題
どんな時に使うのか
- 外部ライブラリや低レベル DOM API を叩く前に、最新の state・DOM を確実に反映させたい時
- スクロール位置、サイズ測定、フォーカス制御など、描画後の DOM を前提とする処理が必要な時
React はレンダリングを最適化するため、複数の state 更新をまとめて処理しています。
しかし、即座に DOM が必要なケースではこの最適化が不都合になることもあります。
そこで以下のように flushSync を使うことで調整します。
実装サンプル:最新 DOM を前提としたスクロール制御
以下はメッセージ追加 → 直後にスクロールを最下部へ移動したい例
import { useRef, useState } from 'react';
import { flushSync } from 'react-dom';
export function Chat() {
const [messages, setMessages] = useState<string[]>([]);
const bottomRef = useRef<HTMLDivElement>(null);
const addMessage = () => {
// ここで flushSync を使うことで、messages が即時レンダーされる
flushSync(() => {
setMessages((prev) => [...prev, '新しいメッセージ']);
});
// DOM が確実に更新された後なので安全にスクロールできる
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
};
return (
<div>
<button onClick={addMessage}>追加</button>
<div style={{ height: 200, overflowY: 'auto', border: '1px solid #ddd' }}>
{messages.map((m, i) => (
<p key={i}>{m}</p>
))}
<div ref={bottomRef} />
</div>
</div>
);
}解説
- flushSync(() => setMessages(...))により、このブロック内の state 更新は同期的に DOM に反映されます
- そのためscrollIntoViewを実行する時点では DOM が確実に更新済み
- React の通常レンダリングの非同期性で発生する「スクロール先がずれる・反映されない」問題を回避できます
注意点
- 過度に使うと React のバッチング最適化が失われ、パフォーマンス低下の原因になります
- 本当に「最新 DOM が必要な場面」に絞って利用するべきです
さいごに
flushSync は常用する API ではなく、DOM を同期的に扱いたい「特殊な状況」でのみ効果を発揮するものです。 React のレンダリングモデルを理解した上で、必要最小限で使うことで、外部ライブラリとの連携やフォーカス制御などを安全に実装できるようになります。