diff --git a/.changeset/fix-react-19-flushsync.md b/.changeset/fix-react-19-flushsync.md
new file mode 100644
index 00000000..143e3b7c
--- /dev/null
+++ b/.changeset/fix-react-19-flushsync.md
@@ -0,0 +1,11 @@
+---
+"@tanstack/react-virtual": patch
+---
+
+fix: defer flushSync to microtask for React 19 compatibility
+
+React 19 throws a warning when `flushSync` is called from inside a lifecycle method (`useLayoutEffect`). This change wraps the `flushSync` call in `queueMicrotask()` to defer it to a microtask, allowing React to complete its current render cycle before forcing the synchronous update.
+
+Fixes #1094
+
+This fixes the warning: "flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering."
diff --git a/packages/react-virtual/src/index.tsx b/packages/react-virtual/src/index.tsx
index d835e7c4..a7da4a0e 100644
--- a/packages/react-virtual/src/index.tsx
+++ b/packages/react-virtual/src/index.tsx
@@ -28,7 +28,7 @@ function useVirtualizerBase<
...options,
onChange: (instance, sync) => {
if (sync) {
- flushSync(rerender)
+ queueMicrotask(() => flushSync(rerender))
} else {
rerender()
}
diff --git a/packages/react-virtual/tests/index.test.tsx b/packages/react-virtual/tests/index.test.tsx
index c7348dc2..05c69c77 100644
--- a/packages/react-virtual/tests/index.test.tsx
+++ b/packages/react-virtual/tests/index.test.tsx
@@ -138,7 +138,7 @@ test('should render given dynamic size', async () => {
expect(renderer).toHaveBeenCalledTimes(3)
})
-test('should render given dynamic size after scroll', () => {
+test('should render given dynamic size after scroll', async () => {
render(
)
expect(screen.queryByText('Row 0')).toBeInTheDocument()
@@ -153,6 +153,9 @@ test('should render given dynamic size after scroll', () => {
target: { scrollTop: 400 },
})
+ // Wait for microtask to complete (flushSync is deferred via queueMicrotask)
+ await new Promise((resolve) => queueMicrotask(() => resolve()))
+
expect(screen.queryByText('Row 2')).not.toBeInTheDocument()
expect(screen.queryByText('Row 3')).toBeInTheDocument()
expect(screen.queryByText('Row 6')).toBeInTheDocument()