v2: Android view flattening removes ViewHolderCollection's container, breaking row positions (missing contentContainerStyle padding + ListHeaderComponent offset)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 7.2k
- Forks
- 393
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 1
Description
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
contentContainerStylehorizontal padding → rows render flush against the screen edge - the
ListHeaderComponentheight → rows overlap the header, and because absolute children
contribute no size, the container's explicitheightalso 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=0overlapping 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
- iOS (reproduces less frequently on iOS)
- Android
- Web (if applicable)
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
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/recyclerview/ViewHolderCollection.tsx, focusing on the CompatView that wraps the absolutely positioned rows and comparing it with the outer RecyclerView container. Run the provided Android New Architecture reproduction, remounting the list with contentContainerStyle padding and a ListHeaderComponent; done means rows preserve both offsets and the scroll range is correct.
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
- 72/100