Fast Lists in React Native Without Sizing Workarounds or Native Modules
If you've ever built a complex interface in React Native, you've probably cursed at lists. The standard FlatList starts dropping FPS on long lists and clogs up memory. FlashList from Shopify is noticeably faster thanks to component reuse, but try feeding it elements of unpredictable height like posts with varying amounts of text and images. Micro-freezes during fast scrolling or layout jittering begin when the list tries to guess cell sizes through estimatedItemSize.
Recently, the LegendApp team released the Legend List library. It's a list component written entirely in TypeScript, without a single line of native code, that solves exactly this dynamic sizing headache.
The Concept
The main goal of the project is to provide a replacement for FlatList and FlashList with a familiar API, but without performance drops on variable-height elements. The developers deliberately used only pure JavaScript and TypeScript. No native modules need to be linked. This simplifies building for Expo Go and web versions, and also reduces the risk of breaking something when updating the React Native version.
The project is lightweight and works as a drop-in replacement. If you already have a screen written with FlatList, a basic migration only requires changing the import and component name.
What the Library Can Do
The developers focused on typical problems with lists in mobile apps. Here are four things that are noticeably more convenient here:
Dynamic height out of the box. You don't need to pre-calculate exact card dimensions or pass approximate estimates. The list adapts to content sizes on its own without render lag.
Proper chat interface. The classic trick for creating a messaging screen is to flip the list through
inverted. This causes weird bugs with keyboard animations, inverted scrolling, and positioning. Legend List has aalignItemsAtEndprop that aligns content to the bottom edge without inversion, andmaintainScrollAtEndthat keeps scroll at the last message when new data arrives.Bidirectional infinite scroll. Loading data up and down works without white screen flashes and abrupt jumps in viewport position.
Controlled cell recycling. Through the
recycleItemsprop, you can enable or disable component reuse. If a complex localuseStateis stored inside a list element, reuse sometimes leads to unpleasant side effects with displaying someone else's state. In Legend List, recycling is disabled by default for safety, but you can enable it with a single line for maximum speed.
What It Looks Like in Code
Installation is standard:
npm install @legendapp/list
# или через bun / yarn
bun add @legendapp/list
We import the component from the runtime subfolder and pass familiar props:
import React, { useRef } from "react"
import { View, Image, Text, StyleSheet } from "react-native"
import { LegendList, LegendListRef, LegendListRenderItemProps } from "@legendapp/list/react-native"
interface PostItem {
id: string
author: string
content: string
photoUrl?: string
}
export const FeedScreen = ({ posts }: { posts: PostItem[] }) => {
const listRef = useRef<LegendListRef | null>(null)
const renderItem = ({ item }: LegendListRenderItemProps<PostItem>) => {
return (
<View style={styles.card}>
<Text style={styles.author}>{item.author}</Text>
<Text style={styles.text}>{item.content}</Text>
{item.photoUrl && (
<Image source={{ uri: item.photoUrl }} style={styles.image} />
)}
</View>
)
}
return (
<LegendList
data={posts}
renderItem={renderItem}
keyExtractor={(item) => item.id}
recycleItems={true}
maintainVisibleContentPosition
ref={listRef}
/>
)
}
const styles = StyleSheet.create({
card: { padding: 16, borderBottomWidth: 1, borderColor: "#eee" },
author: { fontWeight: "bold", marginBottom: 4 },
text: { fontSize: 14, lineHeight: 20 },
image: { width: "100%", height: 200, marginTop: 8, borderRadius: 8 },
})
The maintainVisibleContentPosition prop keeps content on screen if element sizes above the visible area change or new data arrives.
Limitations and Plans
The repository is young, it appeared at the end of 2024. In the roadmap, the authors openly state what's not there yet:
- Masonry grids (tiles of varying heights)
- Sticky headers
- Column spans
- Separate item types (
getItemType)
If your screen relies on a complex grid with sticky sections, it's too early to migrate.
Who Should Try It
The library is useful for those tired of fighting interface jumps in chats, news feeds, or comment lists. If FlashList jitters on your dynamic cards and FlatList doesn't provide the smoothness you need, install @legendapp/list in a test branch. It takes a couple of minutes to check since you won't need to change component structure or write native wrappers.
Related projects