Shopify / Shopify/flash-list

[iOS] Bottom items not rendered when scrolling to end with variable-height items (Android works fine)

Open
#1,951 5 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P1
Dominant language
TypeScript
Stars
7.2k
Forks
392
Avg merge
1d 3h
Merged PRs (30d)
1

Description

Description

When using FlashList v2 with dynamic-sized list items (form fields with varying heights), the bottom section of the list is not rendered on iOS when scrolling to the end.

The same code works perfectly on Android, where all items are rendered correctly.

This issue is not related to the keyboard or inputs expanding — it occurs even with static items that have variable heights.

Current behavior

On iOS:

  • The last few items are not displayed.
  • The list footer and padding are visible, meaning the list's end is calculated, but the items just above the footer are not rendered
  • Increasing drawDistance values fixes the rendering issue but causes performance problems and inconsistent behavior on other forms with different item counts

On Android:

  • All items render as expected with no issues

Steps to reproduce:

  1. Create a FlashList with 40–50+ form fields of varying heights (text inputs, dropdowns, date pickers, signature fields, etc.)
  2. Scroll to the bottom of the list on iOS
  3. Observe that some bottom items are missing or not rendered until you slightly scroll back up
  4. Run the same code on Android — the full list is visible and renders correctly

Expected behavior

All list items should render correctly on iOS, just as they do on Android, when scrolling to the bottom. The list should not require platform-specific drawDistance calculations to render all items properly.

Reproduction

⚠️ Note: This issue occurs in a complex production app with form fields and dynamic content. A minimal reproduction would require setting up:

  • Multiple form field types with varying heights
  • 40-50+ items in the list
  • ScrollToIndex functionality
  • Dynamic content updates

Code from production app:

<FlashList
  ref={flatListRef}
  data={fields}
  extraData={[fields, formData?.defined, formHidden]}
  renderItem={renderItem}
  keyExtractor={(item, index) => {
    const baseKey = item.id || item.dbId || `item_${index}`;
    const typeKey = item.type || 'unknown';
    return `${baseKey}_${typeKey}_${index}`;
  }}
  getItemType={item => item.type || 'default'}
  // === ISSUE: drawDistance needs platform-specific adjustment ===
  // This workaround doesn't fully work - causes issues on some forms
  drawDistance={
    Platform.OS === 'ios'
      ? Math.round(
          screenContext.windowHeight *
            Math.max(1.321, visibleFields?.length / 42),
        )
      : Math.round(screenContext.windowHeight) // Works fine on Android
  }
  maxItemsInRecyclePool={100}
  maintainVisibleContentPosition={{
    minIndexForVisible: 0,
    autoscrollToTopThreshold: 1,
  }}
  overrideProps={{
    getItemCount: () => visibleFields?.length || 0,
    getItem: (data, index) => data?.[index],
  }}
  removeClippedSubviews={false}
  scrollEventThrottle={32}
  decelerationRate="fast"
  nestedScrollEnabled={true}
  keyboardDismissMode="interactive"
  keyboardShouldPersistTaps="always"
  contentContainerStyle={{
    paddingBottom: screenContext.windowHeight * 0.05,
  }}
  viewabilityConfig={{
    minimumViewTime: 100,
    itemVisiblePercentThreshold: 10,
  }}
/>

Expo Snack or minimal reproduction link:

Currently not available - this occurs in a production React Native app with complex form logic. Will work on creating a minimal reproduction if needed.

Platform

  • iOS
  • Android (works correctly)
  • Web

Environment

React Native info output:
System:
  OS: macOS 26.0.1
  CPU: (10) arm64 Apple M4
  Memory: 143.42 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 23.11.0
    path: /opt/homebrew/bin/node
  Yarn:
    version: 1.22.22
    path: /opt/homebrew/bin/yarn
  npm:
    version: 10.9.2
    path: /opt/homebrew/bin/npm
  Watchman:
    version: 2025.04.14.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/saif/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.0
      - iOS 26.0
      - macOS 26.0
      - tvOS 26.0
      - visionOS 26.0
      - watchOS 26.0
  Android SDK:
    API Levels:
      - "35"
    Build Tools:
      - 35.0.0
    Android NDK: Not Found
IDEs:
  Android Studio: 2025.1 AI-251.26094.121.2513.14007798
  Xcode:
    version: 26.0.1/17A400
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.15
    path: /usr/bin/javac
  Ruby:
    version: 3.2.2
    path: /Users/saif/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.0.0
    wanted: 20.0.0
  react:
    installed: 19.1.0
    wanted: 19.1.0
  react-native:
    installed: 0.81.4
    wanted: 0.81.4
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

FlashList version: 2.1.0

Additional context

Attempted Workaround (Not Fully Working)

We've tried implementing a platform-specific dynamic drawDistance calculation for iOS:

drawDistance={
  Platform.OS === 'ios'
    ? Math.round(windowHeight * Math.max(1.321, visibleFields.length / 42))
    : Math.round(windowHeight)
}

Why this workaround fails:

  • ❌ It requires magic numbers (1.321, 42) that don't scale properly
  • ❌ Different forms with different item counts need different multipliers
  • ❌ When the multiplier is too high, it causes rendering issues and performance problems on some forms
  • ❌ When the multiplier is too low, bottom items don't render on other forms
  • ❌ It's unreliable and inconsistent across different forms in the same app
  • ✅ Android works perfectly with just Math.round(windowHeight) without any adjustments

The core issue: There's no reliable formula to calculate the correct drawDistance for iOS across different forms with varying item counts and heights. The value that fixes one form breaks another.

Analysis
  • This happens only on iOS (tested on iOS 17.x, both simulator and real devices)
  • Keyboard, SafeAreaView, and TextInput resizing are not related to the issue
  • Appears to be a viewport measurement or virtualization issue specific to iOS layout calculations
  • Setting a higher drawDistance (e.g., windowHeight * 1.5 or using a formula based on item count) fixes the rendering but introduces other problems
Key Questions
  1. Why does iOS require a larger drawDistance than Android for the same content?
  2. Is there a way to make FlashList automatically calculate the correct drawDistance based on the actual content height?
  3. Can the viewport prediction algorithm be improved to handle variable-height items more accurately on iOS?

Root cause hypothesis: FlashList v2's viewport prediction on iOS might slightly under-measure the scrollable area when dealing with variable-height items, causing the last few items to fall outside the visible rendering window until re-scrolled. The Android implementation appears to handle this correctly.

Checklist

  • I've searched existing issues and couldn't find a duplicate
  • I've provided a minimal reproduction (working on it)
  • 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

The issue names no source files or tests and does not include a minimal reproduction. Start by reducing the production setup to a reproducible iOS case with variable-height items, then trace the list virtualization and viewport calculations. Done means the bottom items render without a platform-specific drawDistance workaround while Android behavior remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
react-native, typescript
Domain
mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.