Every React Native app eventually hits the list problem: a feed, a product grid, or a chat that stutters, shows blank rows on fast scrolls, or eats memory. FlatList is the built-in answer. FlashList by Shopify is the popular high-performance alternative. FlashList v2, a ground-up rewrite for the New Architecture, makes the choice easier than ever.
How they differ under the hood
FlatList is a virtualized list. It renders a window of items around the viewport and unmounts and remounts components as you scroll. Each new row means new component instances, new native views, and more garbage collection.
FlashList recycles views. When a row scrolls off-screen, its native view is reused for the next row coming in, and only the data changes. It's the same idea as UICollectionView on iOS and RecyclerView on Android, which is why it holds 60 fps on long, image-heavy lists.
What changed in FlashList v2
- No more size estimates.
estimatedItemSize,estimatedListSize, andestimatedFirstItemOffsetare gone. v2 measures items synchronously using the New Architecture and positions them precisely. - New Architecture only. v2 doesn't run on the old architecture. (Expo SDK 55+ is New Architecture by default.)
- Masonry is a prop.
MasonryFlashListwas replaced bymasonryon the regularFlashList. - No
inverted. For chat UIs, usemaintainVisibleContentPositioninstead. - JS-only implementation, which makes it easier to debug and contribute to.
Install
npx expo install @shopify/flash-list
Basic usage
The API is intentionally close to FlatList. Most of the time, you just swap the import:
import { FlashList } from "@shopify/flash-list"
type Product = { id: string; title: string; price: number }
export function ProductList({ products }: { products: Product[] }) {
return (
<FlashList
data={products}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <ProductRow product={item} />}
/>
)
}
No estimatedItemSize is needed. If you're migrating from v1, delete it.
Mixed item types: use getItemType
Recycling works best when a recycled view is reused for a similar item. If your list mixes headers, ads, and product rows, tell FlashList which is which:
<FlashList
data={feed}
keyExtractor={(item) => item.id}
getItemType={(item) => item.type} // "header" | "product" | "ad"
renderItem={({ item }) => {
if (item.type === "header") return <SectionHeader title={item.title} />
if (item.type === "ad") return <AdCard ad={item} />
return <ProductRow product={item} />
}}
/>
This is the single biggest performance setting for heterogeneous lists.
Masonry grids (Pinterest-style)
<FlashList
data={photos}
masonry
numColumns={2}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Image source={item.uri} style={{ aspectRatio: item.ratio, borderRadius: 12 }} />
)}
/>
Each item keeps its own height and is placed into the shortest column. Pair it with expo-image for caching and smooth image loading.
Chat lists without inverted
Chat UIs used to rely on inverted. In v2, keep the list in normal order and let FlashList hold the scroll position when new content arrives:
<FlashList
data={messages}
keyExtractor={(m) => m.id}
renderItem={({ item }) => <MessageBubble message={item} />}
maintainVisibleContentPosition={{ autoscrollToBottomThreshold: 0.2, startRenderingFromBottom: true }}
onStartReached={loadOlderMessages}
/>
The recycling gotcha: local state
Because components are reused, useState inside a row can leak from one item to the next. For example, a "liked" heart might appear on the wrong row. Either keep per-item state in your data or store, or use FlashList's useRecyclingState, which resets when the item changes:
import { useRecyclingState } from "@shopify/flash-list"
function ProductRow({ product }: { product: Product }) {
const [expanded, setExpanded] = useRecyclingState(false, [product.id])
// ...
}
Migrating from FlatList (or FlashList v1)
- Make sure you're on the New Architecture (any Expo SDK 55+ app).
- Replace
FlatListwithFlashList, keepingdata,renderItem, andkeyExtractor. - Delete
estimatedItemSizeand the other estimate props. - Add
getItemTypefor mixed lists. - Replace
MasonryFlashListwithmasonry, andinvertedwithmaintainVisibleContentPosition. - Audit
useStatein row components for recycling leaks. - Measure in a release build. Dev mode isn't representative.
Performance tips for any list
- Memoize row components (
React.memo) and keeprenderItemstable. - Don't create inline objects or closures for every row when you can avoid it.
- Use
expo-imagewith fixed dimensions oraspectRatiofor images. - Keep rows shallow. Deep view trees cost more than item count.
- Avoid animating layout props inside rows. Animate
transformandopacitywith Reanimated 4.
Verdict
- Use FlashList v2 for feeds, product grids, chats, search results, and anything long or image-heavy.
- Use FlatList for short, static lists where adding a dependency isn't worth it.
- Use Expo UI lists for native-styled settings and forms. They recycle rows as of SDK 58, as covered in our Expo UI guide.
Building an e-commerce or social app? Start from one of our Expo templates instead of a blank screen, and apply the tips above as you add your own lists.