Shopify / Shopify/flash-list

Stale ViewHolder onLayout can throw when its layout index no longer exists

Open Beginner friendly
#2,291 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

agent-triage P1
Dominant language
TypeScript
Stars
7.2k
Forks
393
Avg merge
1d 3h
Merged PRs (30d)
1

Description

Description

ViewHolder.onLayout can report an item size after the list's data/layout table has already changed. When that happens, validateItemSize() still uses the render-time index captured by the ViewHolder, and currently reads the stored layout with recyclerViewManager.getLayout(index).

If the stale index is no longer present in the layout manager, getLayout(index) throws index out of bounds, not enough layouts, which can crash the app during fast list transitions or navigation away from a screen.

The relevant current code path is:

Current behavior

When an obsolete ViewHolder layout callback runs after the underlying FlashList layouts have changed, validateItemSize() calls:

const layout = recyclerViewManager.getLayout(index);

If that render-time index no longer exists, LayoutManager.getLayout() throws:

index out of bounds, not enough layouts

This can happen during fast data changes or screen transitions where React Native/native layout callbacks from old cells arrive after FlashList has already updated the layout table.

Expected behavior

Stale layout callbacks for indexes that no longer exist should be ignored. Current indexes should continue through the existing width/height validation.

A minimal defensive change is to use tryGetLayout(index) in validateItemSize():

const layout = recyclerViewManager.tryGetLayout(index);
if (layout === undefined) {
  return;
}

That preserves the existing validation for current layouts while avoiding a crash for obsolete measurements.

Reproduction

Expo Snack or minimal reproduction link:

I do not have a standalone Expo Snack yet. This was observed in an app flow where an inverted FlashList report/chat list is navigated away from immediately after its data changes, allowing a stale native onLayout callback from an old ViewHolder to arrive after the list's layout table has already changed.

Reduced sequence:

  1. Render a FlashList with enough items to create/recycle multiple ViewHolders.
  2. Trigger a data change that reduces or replaces the layout table.
  3. Immediately navigate away from the screen or otherwise transition the list while item layout callbacks can still be delivered.
  4. A stale ViewHolder.onLayout calls onSizeChanged(index, size).
  5. validateItemSize() calls getLayout(index) for an index that no longer exists and throws.

Platform

  • iOS
  • Android
  • Web (if applicable)

Environment

React Native info output:
Not available from the reduced upstream report yet.

FlashList version: 2.3.0. The same validateItemSize() / getLayout(index) code path also appears to exist on current main at the links above.

Additional context

We are carrying a local package patch with the tryGetLayout(index) guard described above, and it prevents this crash without changing the validation behavior for active/current item indexes.

Checklist

  • I've searched existing issues and couldn't find a duplicate
  • I've provided a minimal reproduction (Expo Snack preferred)
  • I'm using the latest version of @shopify/flash-list
  • I've included all required information above

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/recyclerview/RecyclerView.tsx at validateItemSize() and compare its getLayout(index) call with tryGetLayout() in src/recyclerview/RecyclerViewManager.ts. Verify that an absent layout is ignored while current indexes retain the existing width and height validation, then run the relevant project tests or checks.

Written by the indexing model from the issue text.

Assessment

Tech stack
react-native, typescript
Domain
mobile
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.