Description
FlashList v2 positions every row with position: absolute inside a plain container view
(ViewHolderCollection's CompatView). That container is usually a layout-only view, so React
Native's view flattening (New Architecture / Fabric, historically Android) can remove it from the
native hierarchy. When that happens the absolutely-positioned rows are re-anchored to the
ScrollView's content view and lose two offsets at once:
- the
contentContainerStyle horizontal padding → rows render flush against the screen edge
- the
ListHeaderComponent height → rows overlap the header, and because absolute children
contribute no size, the container's explicit height also stops contributing to the scrollable
content, corrupting the scroll range
The outer RecyclerView container already sets collapsable={false}, but the row container does
not. Verified still present in main (src/recyclerview/ViewHolderCollection.tsx, the
<CompatView style={hasData && containerStyle}>).
Current behavior
On Android (frequently) and iOS (rarely — flattening eligibility is re-evaluated per commit, and
the container's opacity: 0 → 1 first-commit workaround masks it most of the time), a mounted
list renders with rows at x = 0 ignoring content padding, first row underneath the
ListHeaderComponent, and a wrong scroll extent.
Evidence from a broken instance (chat UI, Android emulator, RN 0.85 new arch):
- FlashList's own model is correct:
ref.getWindowSize().width = 350 (padded width),
ref.getFirstItemOffset() = 128.67 (header height), row layouts measured — all sane.
- The native hierarchy disagrees: rows render at
x=0, width 943px (unpadded), first row at
y=0 overlapping the header, content not scrollable.
Adding collapsable={false} to the ViewHolderCollection container fixes both symptoms
immediately (verified on-device).
Expected behavior
Rows are positioned inside the padded content area, after the ListHeaderComponent, with a correct
scroll range — i.e. the container that anchors the absolute rows is never removed by view
flattening, matching the collapsable={false} already applied to the outer container.
Reproduction
Expo Snack or minimal reproduction link: (minimal component below; note the probabilistic
nature — flattening is decided per commit, so a single mount may render correctly. Remounting
the list repeatedly triggers it reliably on Android in our app.)
const DATA = Array.from({ length: 20 }, (_, i) => `Item ${i}`);
function Repro() {
const [visible, setVisible] = useState(true);
return (
<View style={{ flex: 1 }}>
<Button title="remount list" onPress={() => { setVisible(false); requestAnimationFrame(() => setVisible(true)); }} />
{visible && (
<FlashList
data={DATA}
renderItem={({ item }) => <Text style={{ height: 40 }}>{item}</Text>}
contentContainerStyle={{ paddingHorizontal: 24 }}
ListHeaderComponent={<View style={{ height: 120 }} />}
/>
)}
</View>
);
}
Platform
Environment
React Native info output:
System:
OS: macOS 26.5
CPU: (14) arm64 Apple M3 Max
Memory: 206.91 MB / 36.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 24.15.0
Yarn:
version: 4.17.1
npm:
version: 11.12.1
Watchman:
version: 2026.01.12.00
Managers:
CocoaPods:
version: 1.16.2
SDKs:
iOS SDK:
Platforms:
- iOS 26.2 (and other 26.2 platforms)
Android SDK: Not Found # CLI quirk — SDK managed via Android Studio; app builds/runs fine
IDEs:
Android Studio: 2025.2 AI-252.28238.7.2523.14688667
Xcode:
version: 26.3/17C529
Languages:
Java:
version: 25.0.2 (Gradle pinned to Temurin 21)
Ruby:
version: 3.3.1
npmPackages:
react:
installed: 19.2.3
react-native:
installed: 0.85.3
Android:
hermesEnabled: Not found # Expo-managed project — CLI can't read it; Hermes enabled
newArchEnabled: Not found # confirmed true in generated android/gradle.properties
iOS:
hermesEnabled: Not found
newArchEnabled: Not found
Expo SDK 56, New Architecture enabled (newArchEnabled=true), Hermes. Reproduced on Android
emulator (Medium Phone, API 36.1) and on iOS (intermittently).
FlashList version: 2.3.1 (bug confirmed still present in 2.3.2 / current main source)
Additional context
- Root cause matches the documented purpose of collapsable: views that are measured or act as
coordinate anchors must opt out of flattening (https://reactnative.dev/architecture/view-flattening).
- The intermittency maps to flattening eligibility toggling across commits: the container renders
with opacity: 0 on first commit (not layout-only → kept), then opacity: 1 (eligible →
may be culled on a later commit). Different mount/update sequences → different outcomes.
- This may be the underlying mechanism behind the known-issues note that FlashList "can't read"
contentContainerStyle padding, and possibly some Android new-arch mispositioning reports.
- Workaround we're shipping: a one-line patch adding collapsable: false to the container, plus
moving horizontal padding onto rows per the docs' recommendation.
Checklist
Description
FlashList v2 positions every row with
position: absoluteinside a plain container view(
ViewHolderCollection'sCompatView). That container is usually a layout-only view, so ReactNative's view flattening (New Architecture / Fabric, historically Android) can remove it from the
native hierarchy. When that happens the absolutely-positioned rows are re-anchored to the
ScrollView's content view and lose two offsets at once:
contentContainerStylehorizontal padding → rows render flush against the screen edgeListHeaderComponentheight → rows overlap the header, and because absolute childrencontribute no size, the container's explicit
heightalso stops contributing to the scrollablecontent, corrupting the scroll range
The outer RecyclerView container already sets
collapsable={false}, but the row container doesnot. Verified still present in
main(src/recyclerview/ViewHolderCollection.tsx, the<CompatView style={hasData && containerStyle}>).Current behavior
On Android (frequently) and iOS (rarely — flattening eligibility is re-evaluated per commit, and
the container's
opacity: 0 → 1first-commit workaround masks it most of the time), a mountedlist renders with rows at
x = 0ignoring content padding, first row underneath theListHeaderComponent, and a wrong scroll extent.Evidence from a broken instance (chat UI, Android emulator, RN 0.85 new arch):
ref.getWindowSize().width= 350 (padded width),ref.getFirstItemOffset()= 128.67 (header height), row layouts measured — all sane.x=0, width 943px (unpadded), first row aty=0overlapping the header, content not scrollable.Adding
collapsable={false}to the ViewHolderCollection container fixes both symptomsimmediately (verified on-device).
Expected behavior
Rows are positioned inside the padded content area, after the ListHeaderComponent, with a correct
scroll range — i.e. the container that anchors the absolute rows is never removed by view
flattening, matching the
collapsable={false}already applied to the outer container.Reproduction
Expo Snack or minimal reproduction link: (minimal component below; note the probabilistic
nature — flattening is decided per commit, so a single mount may render correctly. Remounting
the list repeatedly triggers it reliably on Android in our app.)
Platform
Environment
React Native info output:
Expo SDK 56, New Architecture enabled (
newArchEnabled=true), Hermes. Reproduced on Androidemulator (Medium Phone, API 36.1) and on iOS (intermittently).
FlashList version: 2.3.1 (bug confirmed still present in 2.3.2 / current
mainsource)Additional context
coordinate anchors must opt out of flattening (https://reactnative.dev/architecture/view-flattening).
with opacity: 0 on first commit (not layout-only → kept), then opacity: 1 (eligible →
may be culled on a later commit). Different mount/update sequences → different outcomes.
contentContainerStyle padding, and possibly some Android new-arch mispositioning reports.
moving horizontal padding onto rows per the docs' recommendation.
Checklist