Skip to content

fix(sticky-headers): skip compute when layouts lag behind data - #2513

Open
YuEfSaEDU wants to merge 1 commit into
Shopify:mainfrom
YuEfSaEDU:fix/sticky-headers-layout-oob-2509
Open

YuEfSaEDU wants to merge 1 commit into
Shopify:mainfrom
YuEfSaEDU:fix/sticky-headers-layout-oob-2509

Conversation

@YuEfSaEDU

Copy link
Copy Markdown

Root cause: the early-return guard in StickyHeaders.compute() validated sticky indices against the data length, but the binary search then indexed the layout array through the throwing getLayout(). Layouts only grow in processDataUpdate() (gated on hasLayout()), so on the frame right after a data change the arrays legitimately diverge and getLayout throws index out of bounds, not enough layouts.

Two changes:

  1. Skip the compute frame while the highest sticky index has no layout yet (tryGetLayout(sortedIndices[sortedIndices.length - 1]) undefined). A layout for the last index guarantees one for every lower sticky index since layouts are a dense array, so this one check covers the whole lag window.
  2. The binary-search comparator now reads tryGetLayout(index)?.y ?? 0, matching the three sibling reads already in the same function.

In steady state layouts.length >= dataLength, so the guard is a no-op on the normal path.

Tests: new regression test should skip compute without throwing when data length exceeds layout count — against the unfixed code it fails with the exact production error from the issue; with the fix, mount and scroll during the lag window skip cleanly and compute resumes once layouts catch up. The mock factory gained a layoutCount knob that mirrors the real LayoutManager contract (getLayout throws, tryGetLayout returns undefined). StickyHeaders 20/20, RecyclerView 19/19, full unit suite 15 suites 200/200, tsc --noEmit and prettier clean.

Fixes #2509

StickyHeaders.compute() validated sticky indices against the data
array length, but the binary search that follows indexes the layout
array via getLayout(index), which throws "index out of bounds, not
enough layouts" when the index is beyond the layouts computed so far.

The two arrays are updated in separate passes: getDataLength() reads
props on every render while layouts only grow in processDataUpdate(),
which is gated on hasLayout(). When data grows while no layout manager
exists, a scroll event landing in that window crashed the scroll
handler.

compute() now skips the frame when the last sticky index has no layout
yet (a layout for the last index guarantees one for every other sticky
index) and the binary search reads positions through tryGetLayout,
matching the other layout reads in the same function.

Fixes Shopify#2509
@YuEfSaEDU

Copy link
Copy Markdown
Author

I have signed the CLA!

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

1 participant