Skip to content

Commit 1686256

Browse files
authored
feat(react-virtual): add useFlushSync option (#1100)
1 parent a1d0043 commit 1686256

File tree

4 files changed

+71
-9
lines changed

4 files changed

+71
-9
lines changed

.changeset/angry-poets-visit.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@tanstack/react-virtual': patch
3+
---
4+
5+
feat(react-virtual): add `useFlushSync` option
6+
7+
Adds a React-specific `useFlushSync` option to control whether `flushSync` is used for synchronous scroll correction during measurement.
8+
9+
The default behavior remains unchanged (`useFlushSync: true`) to preserve the best scrolling experience.
10+
Disabling it avoids the React 19 warning about calling `flushSync` during render, at the cost of potentially increased visible whitespace during fast scrolling with dynamically sized items.

docs/framework/react/react-virtual.md

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The `@tanstack/react-virtual` adapter is a wrapper around the core virtual logic
99
```tsx
1010
function useVirtualizer<TScrollElement, TItemElement = unknown>(
1111
options: PartialKeys<
12-
VirtualizerOptions<TScrollElement, TItemElement>,
12+
ReactVirtualizerOptions<TScrollElement, TItemElement>,
1313
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
1414
>,
1515
): Virtualizer<TScrollElement, TItemElement>
@@ -22,7 +22,7 @@ This function returns a standard `Virtualizer` instance configured to work with
2222
```tsx
2323
function useWindowVirtualizer<TItemElement = unknown>(
2424
options: PartialKeys<
25-
VirtualizerOptions<Window, TItemElement>,
25+
ReactVirtualizerOptions<Window, TItemElement>,
2626
| 'getScrollElement'
2727
| 'observeElementRect'
2828
| 'observeElementOffset'
@@ -32,3 +32,44 @@ function useWindowVirtualizer<TItemElement = unknown>(
3232
```
3333

3434
This function returns a window-based `Virtualizer` instance configured to work with the window as the scrollElement.
35+
36+
## React-Specific Options
37+
38+
### `useFlushSync`
39+
40+
```tsx
41+
type ReactVirtualizerOptions<TScrollElement, TItemElement> =
42+
VirtualizerOptions<TScrollElement, TItemElement> & {
43+
useFlushSync?: boolean
44+
}
45+
```
46+
47+
Both `useVirtualizer` and `useWindowVirtualizer` accept a `useFlushSync` option that controls whether React's `flushSync` is used for synchronous updates.
48+
49+
- **Type**: `boolean`
50+
- **Default**: `true`
51+
- **Description**: When `true`, the virtualizer will use `flushSync` from `react-dom` to ensure synchronous rendering during scroll events. This provides the most accurate scrolling behavior but may impact performance in some scenarios.
52+
53+
#### When to disable `useFlushSync`
54+
55+
You may want to set `useFlushSync: false` in the following scenarios:
56+
57+
- **React 19 compatibility**: In React 19, you may see the following console warning when scrolling:
58+
```
59+
flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.
60+
```
61+
Setting `useFlushSync: false` will eliminate this warning by allowing React to batch updates naturally.
62+
- **Performance optimization**: If you experience performance issues with rapid scrolling on lower-end devices
63+
- **Testing environments**: When running tests that don't require synchronous DOM updates
64+
- **Non-critical lists**: When slight visual delays during scrolling are acceptable for better overall performance
65+
66+
#### Example
67+
68+
```tsx
69+
const virtualizer = useVirtualizer({
70+
count: 10000,
71+
getScrollElement: () => parentRef.current,
72+
estimateSize: () => 50,
73+
useFlushSync: false, // Disable synchronous updates
74+
})
75+
```

packages/react-virtual/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"test:lib:dev": "pnpm run test:lib --watch",
3131
"test:build": "publint --strict",
3232
"build": "vite build",
33-
"test:e2e": "playwright test"
33+
"test:e2e": "../../node_modules/.bin/playwright test"
3434
},
3535
"type": "module",
3636
"types": "dist/esm/index.d.ts",

packages/react-virtual/src/index.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,29 @@ export * from '@tanstack/virtual-core'
1616
const useIsomorphicLayoutEffect =
1717
typeof document !== 'undefined' ? React.useLayoutEffect : React.useEffect
1818

19+
export type ReactVirtualizerOptions<
20+
TScrollElement extends Element | Window,
21+
TItemElement extends Element,
22+
> = VirtualizerOptions<TScrollElement, TItemElement> & {
23+
useFlushSync?: boolean
24+
}
25+
1926
function useVirtualizerBase<
2027
TScrollElement extends Element | Window,
2128
TItemElement extends Element,
22-
>(
23-
options: VirtualizerOptions<TScrollElement, TItemElement>,
24-
): Virtualizer<TScrollElement, TItemElement> {
29+
>({
30+
useFlushSync = true,
31+
...options
32+
}: ReactVirtualizerOptions<TScrollElement, TItemElement>): Virtualizer<
33+
TScrollElement,
34+
TItemElement
35+
> {
2536
const rerender = React.useReducer(() => ({}), {})[1]
2637

2738
const resolvedOptions: VirtualizerOptions<TScrollElement, TItemElement> = {
2839
...options,
2940
onChange: (instance, sync) => {
30-
if (sync) {
41+
if (useFlushSync && sync) {
3142
flushSync(rerender)
3243
} else {
3344
rerender()
@@ -58,7 +69,7 @@ export function useVirtualizer<
5869
TItemElement extends Element,
5970
>(
6071
options: PartialKeys<
61-
VirtualizerOptions<TScrollElement, TItemElement>,
72+
ReactVirtualizerOptions<TScrollElement, TItemElement>,
6273
'observeElementRect' | 'observeElementOffset' | 'scrollToFn'
6374
>,
6475
): Virtualizer<TScrollElement, TItemElement> {
@@ -72,7 +83,7 @@ export function useVirtualizer<
7283

7384
export function useWindowVirtualizer<TItemElement extends Element>(
7485
options: PartialKeys<
75-
VirtualizerOptions<Window, TItemElement>,
86+
ReactVirtualizerOptions<Window, TItemElement>,
7687
| 'getScrollElement'
7788
| 'observeElementRect'
7889
| 'observeElementOffset'

0 commit comments

Comments
 (0)