Skip to content

fix(VirtualizedList): clear batch timer id on unmount so a Suspense-revived list can schedule updates again - #59123

Open
twelvearrays wants to merge 1 commit into
react:mainfrom
twelvearrays:fix/virtualizedlist-clear-timer-id-on-unmount
Open

twelvearrays wants to merge 1 commit into
react:mainfrom
twelvearrays:fix/virtualizedlist-clear-timer-id-on-unmount

Conversation

@twelvearrays

Copy link
Copy Markdown

Summary:

VirtualizedList.componentWillUnmount clears the pending _updateCellsToRenderTimeoutID timer but never resets the field to null. The low-priority branch of _scheduleCellsToRenderUpdate only schedules a batch when that id is null, so the stale id blocks every future low-priority update on the instance.

Unmount is not final for a class instance inside a Suspense boundary. When already-mounted content is hidden and later revealed — the react-freeze pattern that react-native-screens uses for freezeOnBlur — React calls componentWillUnmount and then componentDidMount on the same instance. If a batch timer was pending at the hide, the revived list can no longer schedule low-priority updates. When the list also had no items at the hide, cellsAroundViewport is {first: 0, last: -1}, so the high-priority path (last >= 0) cannot recover either and the render window stays at initialNumToRender regardless of how much data arrives afterwards.

Field impact: a POS handheld app (react-native-screens with freezeOnBlur) showed 12 of 28 order lines on a screen that was blurred while its list was still empty, and scrolling never rendered the rest.

The fix is one line: reset the timer id to null right after clearTimeout in componentWillUnmount.

Changelog:

[General] [Fixed] - VirtualizedList: clear the pending batch-update timer id in componentWillUnmount so a list hidden and revived by Suspense (react-freeze / freezeOnBlur) resumes rendering new cells

Test Plan:

Added a regression test to packages/virtualized-lists/Lists/__tests__/VirtualizedList-test.js: renders all cells after being hidden and revealed by Suspense with a pending batch update. It renders a VirtualizedList with data={[]} and initialNumToRender={12} under a Suspense boundary on a concurrent root, simulates layout so a low-priority batch timer is pending, hides the subtree by suspending (never-resolving thenable, as react-freeze does), reveals it, updates to 28 items, simulates content layout and scroll, advances fake timers through several batching periods, and asserts that all 28 cells render.

  • Without the fix: Expected length: 28, Received length: 12 (the window never leaves the initial region).
  • With the fix: 28 of 28 cells render and the test passes.

Verified against the published @react-native/virtualized-lists 0.85.3 (same code paths) as well: 12/28 before, 28/28 after. prettier --check passes on both touched files.

…evived list can schedule updates again

`componentWillUnmount` clears the pending `_updateCellsToRenderTimeoutID`
timer but never resets the field to `null`. The low-priority branch of
`_scheduleCellsToRenderUpdate` only schedules a new batch when the id is
`null`, so the stale id blocks every future low-priority update on that
instance.

Unmount is not final for a class instance that sits inside a Suspense
boundary: when already-mounted content is hidden and later revealed
(the react-freeze pattern used by react-native-screens `freezeOnBlur`),
React calls `componentWillUnmount` and then `componentDidMount` on the
same instance. If a batch timer was pending at the hide, the list comes
back unable to schedule low-priority updates. When the list also had no
items at the hide, `cellsAroundViewport` is `{first: 0, last: -1}` and
the high-priority path (`last >= 0`) cannot recover either, so the render
window stays at `initialNumToRender` no matter how much data arrives.

Reproduced in Jest: a list with `initialNumToRender={12}` that is laid
out empty, frozen, revealed and then given 28 items renders 12 cells
forever. With the id reset it renders all 28.

Changelog: [General] [Fixed] - VirtualizedList: clear the pending batch-update timer id in componentWillUnmount so a list hidden and revived by Suspense (react-freeze / freezeOnBlur) resumes rendering new cells
@meta-cla

meta-cla Bot commented Oct 15, 2026

Copy link
Copy Markdown

Hi @twelvearrays!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant