Description
On web, if a FlashList re-renders while its container is hidden (display: none, e.g. an inactive screen or column that stays mounted), the list measures every size as 0 and then mounts every item in the data set at once. With a few thousand items this freezes the app.
Root cause: the layout effects in RecyclerView still run while the container is hidden. On web, measureParentSize and measureItemLayout read clientWidth / clientHeight, which are 0 for an element inside a display: none subtree, so:
- The window size becomes
0 x 0.
- Every rendered item is re-measured as
height: 0, overwriting its real height.
- The height average window (last 5 samples) drops to ~0, so unmeasured items are also estimated at ~0.
All layouts collapse to y = 0, height = 0, so getVisibleLayouts returns the whole data set as engaged.
Current behavior
All items mount at once while the container is hidden. Debug output from RecyclerViewManager.updateRenderStack in our app (inverted chat list with ~6,400 messages, hidden on logout):
engaged 0 5686 count 5687 data 6384 window {"width":0,"height":0}
layoutSize {"width":0,"height":842688} scrollOffset -8 firstItemOffset 0
measured 517 measuredZero 517
0:y=0,h=0,m=1 | 1:y=0,h=0,m=1 | 2:y=0,h=0,m=1 | ...
Stack: updateRenderStack ← updateScrollOffset ← recomputeEngagedIndices ← modifyChildrenLayout ← item-measurement useLayoutEffect in RecyclerView.
Expected behavior
While the container is hidden, FlashList should skip measuring and keep the last valid layouts. Once the container is shown again, it should re-measure and render only the visible window.
Reproduction
Expo Snack or minimal reproduction link: none yet. Minimal web reproduction:
import React, { useState } from "react";
import { Button, Text, View } from "react-native";
import { FlashList } from "@shopify/flash-list";
const makeData = (n: number) => Array.from({ length: n }, (_, i) => i);
export default function App() {
const [hidden, setHidden] = useState(false);
const [data, setData] = useState(() => makeData(5000));
return (
<View style={{ flex: 1 }}>
<Button title="Toggle hidden" onPress={() => setHidden((h) => !h)} />
<Button title="Update data" onPress={() => setData(makeData(5001))} />
<View style={{ flex: 1, display: hidden ? "none" : "flex" }}>
<FlashList
data={data}
keyExtractor={(item) => String(item)}
renderItem={({ item }) => {
console.log("render", item);
return <Text style={{ padding: 16 }}>Item {item}</Text>;
}}
/>
</View>
</View>
);
}
- Run on web. Only the visible window renders.
- Press Toggle hidden, then Update data.
- The console logs
render for all ~5000 items.
Platform
Environment
React Native Web inside Electron. React 19.2.3, TypeScript 5.8.3.
FlashList version: 2.3.3
Additional context
Proposed fix: skip the container and item measurement layout effects while the container reports 0 x 0 on web, and record a 0 x 0 container size so the existing onLayout check re-measures when the container is shown again. PR incoming.
Description
On web, if a FlashList re-renders while its container is hidden (
display: none, e.g. an inactive screen or column that stays mounted), the list measures every size as0and then mounts every item in the data set at once. With a few thousand items this freezes the app.Root cause: the layout effects in
RecyclerViewstill run while the container is hidden. On web,measureParentSizeandmeasureItemLayoutreadclientWidth/clientHeight, which are0for an element inside adisplay: nonesubtree, so:0 x 0.height: 0, overwriting its real height.All layouts collapse to
y = 0, height = 0, sogetVisibleLayoutsreturns the whole data set as engaged.Current behavior
All items mount at once while the container is hidden. Debug output from
RecyclerViewManager.updateRenderStackin our app (inverted chat list with ~6,400 messages, hidden on logout):Stack:
updateRenderStack←updateScrollOffset←recomputeEngagedIndices←modifyChildrenLayout← item-measurementuseLayoutEffectinRecyclerView.Expected behavior
While the container is hidden, FlashList should skip measuring and keep the last valid layouts. Once the container is shown again, it should re-measure and render only the visible window.
Reproduction
Expo Snack or minimal reproduction link: none yet. Minimal web reproduction:
renderfor all ~5000 items.Platform
Environment
React Native Web inside Electron. React 19.2.3, TypeScript 5.8.3.
FlashList version: 2.3.3
Additional context
Proposed fix: skip the container and item measurement layout effects while the container reports
0 x 0on web, and record a0 x 0container size so the existingonLayoutcheck re-measures when the container is shown again. PR incoming.