From harness-claude
Builds performant scrollable lists in React Native using FlatList, SectionList, and FlashList for large datasets, infinite scroll, pull-to-refresh, and grouped sections.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Build performant scrollable lists with FlatList, SectionList, and FlashList for large data sets
Optimizes React Native app performance via FlatList tweaks, React.memo/useMemo, FastImage caching, bundle size reduction, and profiling techniques.
Provides React Native and Flutter patterns for performant lists with FlatList, stack/tab navigation, and widget structure for mobile apps.
Provides React Native best practices for project structure, adaptive styling (NativeWind, StyleSheet), performance-optimized components (memo, Pressable), and FlatList rendering in mobile apps with Expo.
Share bugs, ideas, or general feedback.
Build performant scrollable lists with FlatList, SectionList, and FlashList for large data sets
FlatList for simple lists, SectionList for grouped data, and FlashList for maximum performance.import { FlatList } from 'react-native';
function OrderList({ orders }: { orders: Order[] }) {
return (
<FlatList
data={orders}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <OrderCard order={item} />}
/>
);
}
Always provide keyExtractor. Use a stable unique ID from your data. Never use array index as the key — it breaks reordering and causes incorrect recycling.
Memoize renderItem components to prevent unnecessary re-renders during scrolling.
const OrderCard = memo(function OrderCard({ order }: { order: Order }) {
return (
<View style={styles.card}>
<Text>{order.title}</Text>
<Text>{order.status}</Text>
</View>
);
});
// Stable renderItem reference
const renderItem = useCallback(({ item }: { item: Order }) => <OrderCard order={item} />, []);
getItemLayout when item heights are fixed. This eliminates measurement overhead and enables instant scroll-to-index.<FlatList
data={items}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>
onEndReached.function InfiniteList() {
const [data, setData] = useState<Item[]>([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const loadMore = useCallback(async () => {
if (loading) return;
setLoading(true);
const newItems = await fetchItems(page + 1);
setData((prev) => [...prev, ...newItems]);
setPage((p) => p + 1);
setLoading(false);
}, [page, loading]);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loading ? <ActivityIndicator /> : null}
/>
);
}
refreshing and onRefresh.<FlatList
data={data}
renderItem={renderItem}
refreshing={isRefreshing}
onRefresh={async () => {
setIsRefreshing(true);
const fresh = await fetchItems(1);
setData(fresh);
setIsRefreshing(false);
}}
/>
SectionList for grouped data with headers.import { SectionList } from 'react-native';
const sections = [
{ title: 'Today', data: todayItems },
{ title: 'Yesterday', data: yesterdayItems },
];
<SectionList
sections={sections}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <ItemRow item={item} />}
renderSectionHeader={({ section }) => <Text style={styles.header}>{section.title}</Text>}
stickySectionHeadersEnabled
/>;
npm install @shopify/flash-list
import { FlashList } from '@shopify/flash-list';
<FlashList
data={items}
renderItem={({ item }) => <ItemRow item={item} />}
estimatedItemSize={80} // Required — approximate height in pixels
keyExtractor={(item) => item.id}
/>;
ListEmptyComponent.<FlatList
data={data}
renderItem={renderItem}
ListEmptyComponent={<EmptyState message="No orders yet" />}
ListHeaderComponent={<SearchBar />}
/>
FlatList vs. FlashList: FlatList unmounts items as they scroll off-screen and mounts new ones. FlashList recycles views, updating existing components with new data. FlashList is typically 5-10x faster for large lists. Use FlashList when lists exceed ~100 items or when smooth 60fps scrolling is critical.
Performance tuning props:
removeClippedSubviews={true} — detach offscreen views from the native hierarchymaxToRenderPerBatch={10} — items rendered per batch (lower = more responsive, slower fill)windowSize={5} — number of viewport-heights to render around visible area (lower = less memory, more blank space during fast scrolling)updateCellsBatchingPeriod={50} — milliseconds between batch rendersCommon mistakes:
renderItem (causes re-render every cycle)keyExtractor (defaults to index, breaks recycling)ScrollView for dynamic lists (renders all items at once, no virtualization)onEndReachedThreshold too low (triggers only at the very bottom, feels laggy)https://reactnative.dev/docs/flatlist