--- name: flashlist-patterns description: FlashList high-performance list patterns. Use when implementing lists. --- # FlashList Patterns Skill This skill covers Shopify's FlashList for high-performance lists. ## When to Use Use this skill when: - Implementing any scrollable list - Replacing FlatList - Lists have 50+ items - List performance is critical ## Core Principle **FLASHLIST ALWAYS** - FlashList is 10x faster than FlatList. Use it for all lists. ## Installation ```bash npm install @shopify/flash-list ``` ## Basic Usage ```typescript import { FlashList } from '@shopify/flash-list'; interface Item { id: string; title: string; } function ItemList({ items }: { items: Item[] }): React.ReactElement { return ( ( {item.title} )} estimatedItemSize={60} // Required! keyExtractor={(item) => item.id} /> ); } ``` ## Required Props ### estimatedItemSize ```typescript // Required for FlashList to calculate recycling ``` ## Performance Optimization ### Memoize renderItem ```typescript import { useCallback, memo } from 'react'; const ItemCard = memo(function ItemCard({ item }: { item: Item }) { return ( {item.title} ); }); function ItemList({ items }: { items: Item[] }): React.ReactElement { const renderItem = useCallback( ({ item }: { item: Item }) => , [] ); return ( ); } ``` ### Use keyExtractor ```typescript item.id} // Unique stable key /> ``` ## Different Item Types ### getItemType ```typescript interface ListItem { id: string; type: 'header' | 'item' | 'separator'; data: unknown; } { switch (item.type) { case 'header': return ; case 'separator': return ; default: return ; } }} getItemType={(item) => item.type} // Enables better recycling estimatedItemSize={60} /> ``` ### overrideItemLayout ```typescript { // Set exact size for different item types if (item.type === 'header') { layout.size = 100; } else if (item.type === 'separator') { layout.size = 20; } else { layout.size = 60; } }} /> ``` ## Horizontal Lists ```typescript ``` ## Grid Layout ```typescript ``` ## Pull to Refresh ```typescript import { useState } from 'react'; import { RefreshControl } from 'react-native'; function RefreshableList(): React.ReactElement { const [refreshing, setRefreshing] = useState(false); const onRefresh = async () => { setRefreshing(true); await fetchNewData(); setRefreshing(false); }; return ( } /> ); } ``` ## Infinite Scroll ```typescript function InfiniteList(): React.ReactElement { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const loadMore = async () => { if (loading || !hasMore) return; setLoading(true); const newItems = await fetchMoreItems(); if (newItems.length === 0) { setHasMore(false); } else { setItems((prev) => [...prev, ...newItems]); } setLoading(false); }; return ( : null} /> ); } ``` ## Empty State ```typescript No items found } /> ``` ## Headers and Footers ```typescript Items } ListFooterComponent={ End of list } /> ``` ## Sticky Headers ```typescript interface Section { title: string; data: Item[]; } // Use SectionList-like pattern { if (item.isHeader) { return ; } return ; }} stickyHeaderIndices={headerIndices} estimatedItemSize={60} getItemType={(item) => (item.isHeader ? 'header' : 'item')} /> ``` ## Scroll to Item ```typescript import { useRef } from 'react'; import { FlashList } from '@shopify/flash-list'; function ScrollableList(): React.ReactElement { const listRef = useRef>(null); const scrollToTop = () => { listRef.current?.scrollToOffset({ offset: 0, animated: true }); }; const scrollToIndex = (index: number) => { listRef.current?.scrollToIndex({ index, animated: true }); }; return ( <> ); } ``` ## Common Props ```typescript item.id} getItemType={(item) => item.type} numColumns={1} horizontal={false} inverted={false} showsVerticalScrollIndicator={true} showsHorizontalScrollIndicator={false} onEndReached={loadMore} onEndReachedThreshold={0.5} refreshControl={} ListHeaderComponent={
} ListFooterComponent={