Skip to content

fix(hooks): resync manager after a discarded concurrent render - #2528

Closed
NerdyMihir wants to merge 1 commit into
Shopify:mainfrom
NerdyMihir:fix/discarded-render-resync
Closed

NerdyMihir wants to merge 1 commit into
Shopify:mainfrom
NerdyMihir:fix/discarded-render-resync

Conversation

@NerdyMihir

Copy link
Copy Markdown

Description

Fixes #2527

useRecyclerViewManager calls updateProps(props) and processDataUpdate() during render inside useMemo blocks keyed on [props] / [data]. When React discards a render (Suspense, transitions, useDeferredValue), the manager has already taken the discarded props, but the memo cache from that render is dropped. The next committed render has the same deps as the last commit, so both memos skip and the manager stays on the discarded data. With a longer discarded array, the list renders indices past the committed data: index out of bounds, not enough layouts on 2.3.3, renderItem with item === undefined on 2.0.x.

The fix keeps the last props/data the manager actually received in a useState object and re-syncs whenever the current render's values differ from it. Renders that commit normally behave as before: one updateProps per new props, one processDataUpdate per new data, including the first render.

src/__tests__/useRecyclerViewManager.test.tsx reproduces it on a concurrent react-test-renderer root: a deferred 2→6 row update suspends and is discarded, then an extraData change re-renders. It throws on main and passes with the fix.

Reviewers’ hat-rack 🎩

  • yarn test --forceExit, yarn type-check, yarn lint are green locally
  • Run the new test with the hook change reverted: it fails with index out of bounds, not enough layouts
  • Check whether a list with frequently changing props (no data change) still calls processDataUpdate only when data changes

Screenshots or videos (if needed)

N/A (no visual change)

useRecyclerViewManager pushed props and data into the manager inside
useMemo. A render React discards still mutates the manager, but its memo
cache is dropped, so the next committed render sees unchanged deps and
skips the resync. Compare against what the manager last saw instead.

Fixes Shopify#2527
@NerdyMihir

Copy link
Copy Markdown
Author

Closing; leaving the fix approach described in #2527 for maintainers to take as they prefer.

@NerdyMihir NerdyMihir closed this Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discarded concurrent render leaves RecyclerViewManager ahead of committed data

1 participant