-
-
Notifications
You must be signed in to change notification settings - Fork 408
feat: add deferFlushSync option for React 19 compatibility
#1098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
channyeintun
wants to merge
6
commits into
TanStack:main
Choose a base branch
from
channyeintun:fix/react-19-flushsync-warning
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
2c97816
fix(react-virtual): defer flushSync to microtask for React 19 compati…
channyeintun 59f577b
chore: add changeset for React 19 flushSync fix
channyeintun dc7c52a
test: update scroll test to be async for queueMicrotask change
channyeintun 2398da8
fix: resolve TypeScript error in test
channyeintun e888816
chore: add issue reference #1094
channyeintun fa4acc3
feat: add deferFlushSync option for React 19 compatibility
channyeintun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| --- | ||
| "@tanstack/react-virtual": patch | ||
| "@tanstack/virtual-core": patch | ||
| --- | ||
|
|
||
| feat: add `deferFlushSync` option for React 19 compatibility | ||
|
|
||
| React 19 throws a warning when `flushSync` is called from inside a lifecycle method (`useLayoutEffect`). This change adds a new `deferFlushSync` option that users can enable to defer `flushSync` to a microtask, suppressing the warning. | ||
|
|
||
| **Breaking Change Mitigation**: The default behavior remains unchanged (synchronous `flushSync`) to preserve scroll correction performance. Users experiencing the React 19 warning can opt-in to the deferred behavior by setting `deferFlushSync: true`. | ||
|
|
||
| > **Note**: Enabling `deferFlushSync` may cause visible white space gaps during fast scrolling with dynamic measurements. | ||
|
|
||
| Fixes #1094 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,7 +28,11 @@ function useVirtualizerBase< | |
| ...options, | ||
| onChange: (instance, sync) => { | ||
| if (sync) { | ||
| flushSync(rerender) | ||
| if (options.deferFlushSync) { | ||
| queueMicrotask(() => flushSync(rerender)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop the queueMicrotask |
||
| } else { | ||
| flushSync(rerender) | ||
| } | ||
| } else { | ||
| rerender() | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,7 +85,7 @@ export const observeElementRect = <T extends Element>( | |
| handler(getRect(element as unknown as HTMLElement)) | ||
|
|
||
| if (!targetWindow.ResizeObserver) { | ||
| return () => {} | ||
| return () => { } | ||
| } | ||
|
|
||
| const observer = new targetWindow.ResizeObserver((entries) => { | ||
|
|
@@ -161,12 +161,12 @@ export const observeElementOffset = <T extends Element>( | |
| instance.options.useScrollendEvent && supportsScrollend | ||
| ? () => undefined | ||
| : debounce( | ||
| targetWindow, | ||
| () => { | ||
| cb(offset, false) | ||
| }, | ||
| instance.options.isScrollingResetDelay, | ||
| ) | ||
| targetWindow, | ||
| () => { | ||
| cb(offset, false) | ||
| }, | ||
| instance.options.isScrollingResetDelay, | ||
| ) | ||
|
|
||
| const createHandler = (isScrolling: boolean) => () => { | ||
| const { horizontal, isRtl } = instance.options | ||
|
|
@@ -212,12 +212,12 @@ export const observeWindowOffset = ( | |
| instance.options.useScrollendEvent && supportsScrollend | ||
| ? () => undefined | ||
| : debounce( | ||
| targetWindow, | ||
| () => { | ||
| cb(offset, false) | ||
| }, | ||
| instance.options.isScrollingResetDelay, | ||
| ) | ||
| targetWindow, | ||
| () => { | ||
| cb(offset, false) | ||
| }, | ||
| instance.options.isScrollingResetDelay, | ||
| ) | ||
|
|
||
| const createHandler = (isScrolling: boolean) => () => { | ||
| offset = element[instance.options.horizontal ? 'scrollX' : 'scrollY'] | ||
|
|
@@ -348,6 +348,16 @@ export interface VirtualizerOptions< | |
| enabled?: boolean | ||
| isRtl?: boolean | ||
| useAnimationFrameWithResizeObserver?: boolean | ||
| /** | ||
| * If true, defers the flushSync call during synchronous updates to a microtask. | ||
| * This suppresses the React 19 warning: "flushSync was called from inside a lifecycle method." | ||
| * | ||
| * @warning Enabling this may cause visible white space gaps during fast scrolling | ||
| * with dynamic measurements, as the scroll correction won't be applied synchronously. | ||
| * | ||
| * @default false | ||
| */ | ||
| deferFlushSync?: boolean | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this be only react specific, also let's use useFlushSync |
||
| } | ||
|
|
||
| export class Virtualizer< | ||
|
|
@@ -373,10 +383,10 @@ export class Virtualizer< | |
| shouldAdjustScrollPositionOnItemSizeChange: | ||
| | undefined | ||
| | (( | ||
| item: VirtualItem, | ||
| delta: number, | ||
| instance: Virtualizer<TScrollElement, TItemElement>, | ||
| ) => boolean) | ||
| item: VirtualItem, | ||
| delta: number, | ||
| instance: Virtualizer<TScrollElement, TItemElement>, | ||
| ) => boolean) | ||
| elementsCache = new Map<Key, TItemElement>() | ||
| private observer = (() => { | ||
| let _ro: ResizeObserver | null = null | ||
|
|
@@ -434,7 +444,7 @@ export class Virtualizer< | |
| horizontal: false, | ||
| getItemKey: defaultKeyExtractor, | ||
| rangeExtractor: defaultRangeExtractor, | ||
| onChange: () => {}, | ||
| onChange: () => { }, | ||
| measureElement, | ||
| initialRect: { width: 0, height: 0 }, | ||
| scrollMargin: 0, | ||
|
|
@@ -447,6 +457,7 @@ export class Virtualizer< | |
| isRtl: false, | ||
| useScrollendEvent: false, | ||
| useAnimationFrameWithResizeObserver: false, | ||
| deferFlushSync: false, | ||
| ...opts, | ||
| } | ||
| } | ||
|
|
@@ -605,12 +616,12 @@ export class Virtualizer< | |
|
|
||
| return furthestMeasurements.size === this.options.lanes | ||
| ? Array.from(furthestMeasurements.values()).sort((a, b) => { | ||
| if (a.end === b.end) { | ||
| return a.index - b.index | ||
| } | ||
| if (a.end === b.end) { | ||
| return a.index - b.index | ||
| } | ||
|
|
||
| return a.end - b.end | ||
| })[0] | ||
| return a.end - b.end | ||
| })[0] | ||
| : undefined | ||
| } | ||
|
|
||
|
|
@@ -802,11 +813,11 @@ export class Virtualizer< | |
| return (this.range = | ||
| measurements.length > 0 && outerSize > 0 | ||
| ? calculateRange({ | ||
| measurements, | ||
| outerSize, | ||
| scrollOffset, | ||
| lanes, | ||
| }) | ||
| measurements, | ||
| outerSize, | ||
| scrollOffset, | ||
| lanes, | ||
| }) | ||
| : null) | ||
| }, | ||
| { | ||
|
|
@@ -837,11 +848,11 @@ export class Virtualizer< | |
| return startIndex === null || endIndex === null | ||
| ? [] | ||
| : rangeExtractor({ | ||
| startIndex, | ||
| endIndex, | ||
| overscan, | ||
| count, | ||
| }) | ||
| startIndex, | ||
| endIndex, | ||
| overscan, | ||
| count, | ||
| }) | ||
| }, | ||
| { | ||
| key: process.env.NODE_ENV !== 'production' && 'getVirtualIndexes', | ||
|
|
@@ -960,12 +971,12 @@ export class Virtualizer< | |
| } | ||
| return notUndefined( | ||
| measurements[ | ||
| findNearestBinarySearch( | ||
| 0, | ||
| measurements.length - 1, | ||
| (index: number) => notUndefined(measurements[index]).start, | ||
| offset, | ||
| ) | ||
| findNearestBinarySearch( | ||
| 0, | ||
| measurements.length - 1, | ||
| (index: number) => notUndefined(measurements[index]).start, | ||
| offset, | ||
| ) | ||
| ], | ||
| ) | ||
| } | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lift the check to sync