Skip to content

Fix disappearing cards in masonry when optimizeItemArrangement is dis…#2104

Open
j0sip wants to merge 2 commits intoShopify:mainfrom
j0sip:2103-fix-disappearing-cards
Open

Fix disappearing cards in masonry when optimizeItemArrangement is dis…#2104
j0sip wants to merge 2 commits intoShopify:mainfrom
j0sip:2103-fix-disappearing-cards

Conversation

@j0sip
Copy link
Contributor

@j0sip j0sip commented Feb 16, 2026

Description

Fixes (issue #2103)

This PR fixes an issue in masonry layout where cards unexpectedly disappear as user scrolls when optimizeItemArrangement is disabled.

It adds a new arrays of sorted layout indices to MasonryLayoutManager, which enables correct binary search when calculating visible items. Current implementation of binary search fails when optimizeItemArrangement is disabled, because the array of layouts is not sorted by y position. This leads to incorrect results when column heights are different.

The fix only affects masonry layout and only when optimizeItemArrangement === false, since the issue is not occurring when the prop is enabled.

Reviewers’ hat-rack 🎩

  • [ ]

Screenshots or videos (if needed)

Video (after disabling optimizeItemArrangement in ComplexMasonry from fixture app:

flashlist-fix.mp4

@naqvitalha
Copy link
Collaborator

naqvitalha commented Feb 26, 2026

Thanks for the PR! Binary search on the flat layouts array breaks in masonry with sequential placement because items zigzag between columns (e.g., y-values go [0, 0, 100, 50, 200, 100]), so binary search can miss visible items.

However, the sorting approach adds significant overhead:

  • O(N log N) sort on every layout change (height measurements, new items)
  • O(N) extra memory for sorted copies
  • Rebuild on every processLayoutInfo and recomputeLayouts call

This is avoidable because items within each column are naturally sorted by y. With sequential placement, item i belongs to column i % maxColumns. So you can binary search per column instead of sorting the whole array:

getVisibleLayouts(start: number, end: number): ConsecutiveNumbers {
  if (this.optimizeItemArrangement) {
    return super.getVisibleLayouts(start, end);
  }

  let minIdx = Infinity, maxIdx = -1;

  for (let col = 0; col < this.maxColumns; col++) {
    // Items in column: col, col+maxColumns, col+2*maxColumns, ...
    // Their y-values are sorted — binary search works directly
    const first = this.binarySearchColumn(col, start, true);
    const last = this.binarySearchColumn(col, end, false);
    if (first !== -1) minIdx = Math.min(minIdx, first);
    if (last !== -1) maxIdx = Math.max(maxIdx, last);
  }

  return minIdx > maxIdx
    ? ConsecutiveNumbers.EMPTY
    : new ConsecutiveNumbers(minIdx, maxIdx);
}

private binarySearchColumn(
  col: number, threshold: number, findFirst: boolean
): number {
  // Items in this column at original indices: col, col+mc, col+2*mc, ...
  const mc = this.maxColumns;
  const count = Math.ceil((this.layouts.length - col) / mc);
  let left = 0, right = count - 1, result = -1;

  while (left <= right) {
    const mid = (left + right) >> 1;
    const idx = col + mid * mc;
    const layout = this.layouts[idx];
    const pos = this.horizontal ? layout.x : layout.y;
    const size = this.horizontal ? layout.width : layout.height;

    if (findFirst) {
      if (pos + size > threshold) { result = idx; right = mid - 1; }
      else { left = mid + 1; }
    } else {
      if (pos <= threshold) { result = idx; left = mid + 1; }
      else { right = mid - 1; }
    }
  }
  return result;
}

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.

Disappearing items in Masonry layout

2 participants