install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/mobile-flatlist-patterns" ~/.claude/skills/intense-visions-harness-engineering-mobile-flatlist-patterns && rm -rf "$T"
manifest:
agents/skills/claude-code/mobile-flatlist-patterns/SKILL.mdsource content
FlatList and List Patterns
Build performant scrollable lists with FlatList, SectionList, and FlashList for large data sets
When to Use
- Rendering lists of items that may exceed screen height
- Implementing infinite scroll with pagination
- Building section-grouped lists (contacts, settings)
- Optimizing list rendering performance for hundreds or thousands of items
- Adding pull-to-refresh, swipe actions, or sticky headers
Instructions
- Use
for simple lists,FlatList
for grouped data, andSectionList
for maximum performance.FlashList
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
. Use a stable unique ID from your data. Never use array index as the key — it breaks reordering and causes incorrect recycling.keyExtractor -
Memoize
components to prevent unnecessary re-renders during scrolling.renderItem
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} />, []);
- Set
when item heights are fixed. This eliminates measurement overhead and enables instant scroll-to-index.getItemLayout
<FlatList data={items} getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index, })} />
- Implement infinite scroll with
.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} /> ); }
- Add pull-to-refresh with
andrefreshing
.onRefresh
<FlatList data={data} renderItem={renderItem} refreshing={isRefreshing} onRefresh={async () => { setIsRefreshing(true); const fresh = await fetchItems(1); setData(fresh); setIsRefreshing(false); }} />
- Use
for grouped data with headers.SectionList
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 />;
- Use FlashList for better performance with large lists. FlashList by Shopify uses cell recycling instead of unmounting, providing significantly better scroll performance.
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} />;
- Handle empty states with
.ListEmptyComponent
<FlatList data={data} renderItem={renderItem} ListEmptyComponent={<EmptyState message="No orders yet" />} ListHeaderComponent={<SearchBar />} />
Details
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:
— detach offscreen views from the native hierarchyremoveClippedSubviews={true}
— items rendered per batch (lower = more responsive, slower fill)maxToRenderPerBatch={10}
— number of viewport-heights to render around visible area (lower = less memory, more blank space during fast scrolling)windowSize={5}
— milliseconds between batch rendersupdateCellsBatchingPeriod={50}
Common mistakes:
- Inline arrow functions in
(causes re-render every cycle)renderItem - Missing
(defaults to index, breaks recycling)keyExtractor - Not memoizing list items (every parent re-render re-renders every visible item)
- Using
for dynamic lists (renders all items at once, no virtualization)ScrollView - Setting
too low (triggers only at the very bottom, feels laggy)onEndReachedThreshold
Source
https://reactnative.dev/docs/flatlist
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.