Documentation Index
Fetch the complete documentation index at: https://mintlify.com/facebook/react-native/llms.txt
Use this file to discover all available pages before exploring further.
Optimizing FlatList Performance
Learn how to optimize FlatList for smooth 60 FPS scrolling, even with large datasets. FlatList is React Native’s high-performance list component that virtualizes content.
How FlatList Works
FlatList uses VirtualizedList under the hood to:
- Render only visible items: Items outside viewport are unmounted
- Recycle components: Reuse components as you scroll
- Batch updates: Group multiple updates for efficiency
- Windowing: Keep a buffer of items above and below viewport
Basic FlatList Setup
import React from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
const DATA = Array.from({ length: 1000 }, (_, i) => ({
id: `item-${i}`,
title: `Item ${i}`,
}));
const Item = ({ title }) => (
<View style={styles.item}>
<Text>{title}</Text>
</View>
);
const App = () => {
return (
<FlatList
data={DATA}
renderItem={({ item }) => <Item title={item.title} />}
keyExtractor={item => item.id}
/>
);
};
const styles = StyleSheet.create({
item: {
padding: 20,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
});
Essential Optimizations
1. Implement getItemLayout
Prevent expensive layout calculations by providing item dimensions:
const ITEM_HEIGHT = 80;
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>
Benefits:
- No layout measurements needed
- Instant scroll position calculation
- Smooth
scrollToIndex behavior
Note: Only use if all items have the same height.
Provide stable, unique keys:
// Good: stable unique key
keyExtractor={item => item.id}
// Bad: index as key (can cause issues)
keyExtractor={(item, index) => index.toString()}
// Bad: unstable key
keyExtractor={item => Math.random().toString()}
3. Optimize renderItem with React.memo
Prevent unnecessary re-renders:
import React, { memo } from 'react';
const Item = memo(({ title, onPress }) => {
console.log('Rendering:', title);
return (
<TouchableOpacity onPress={onPress} style={styles.item}>
<Text>{title}</Text>
</TouchableOpacity>
);
});
const App = () => {
const renderItem = useCallback(({ item }) => (
<Item title={item.title} onPress={() => handlePress(item.id)} />
), []);
return (
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
);
};
Control how many items to keep mounted:
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
// Initial items to render
initialNumToRender={10}
// Items to render per batch
maxToRenderPerBatch={10}
// Number of screen lengths to keep in memory
windowSize={5}
// Update cells in batches
updateCellsBatchingPeriod={50}
/>
Parameter Guide:
initialNumToRender: First batch size (default: 10)
maxToRenderPerBatch: Items per subsequent batch (default: 10)
windowSize: Screen lengths to keep mounted (default: 21)
- Lower values = less memory, more blank content during fast scrolling
5. Remove Clipped Subviews
Unmount off-screen components (Android primarily):
<FlatList
data={DATA}
renderItem={renderItem}
removeClippedSubviews={true}
/>
Benefits:
- Reduced memory usage
- Better performance on Android
Caveats:
- May cause issues with animations
- Can have bugs with complex layouts
Advanced Optimizations
Avoid Anonymous Functions
// Bad: creates new function every render
<FlatList
data={DATA}
renderItem={({ item }) => <Item title={item.title} />}
onEndReached={() => loadMore()}
/>
// Good: stable function references
const renderItem = useCallback(({ item }) => (
<Item title={item.title} />
), []);
const handleEndReached = useCallback(() => {
loadMore();
}, []);
<FlatList
data={DATA}
renderItem={renderItem}
onEndReached={handleEndReached}
/>
Optimize Images
import FastImage from 'react-native-fast-image';
const Item = memo(({ imageUrl, title }) => (
<View style={styles.item}>
<FastImage
source={{
uri: imageUrl,
priority: FastImage.priority.normal,
}}
style={styles.image}
resizeMode={FastImage.resizeMode.cover}
/>
<Text>{title}</Text>
</View>
));
Use ListEmptyComponent
const EmptyComponent = () => (
<View style={styles.empty}>
<Text>No items found</Text>
</View>
);
<FlatList
data={DATA}
renderItem={renderItem}
ListEmptyComponent={EmptyComponent}
/>
Implement Pull to Refresh
import { RefreshControl } from 'react-native';
const App = () => {
const [refreshing, setRefreshing] = useState(false);
const onRefresh = useCallback(async () => {
setRefreshing(true);
await fetchData();
setRefreshing(false);
}, []);
return (
<FlatList
data={DATA}
renderItem={renderItem}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
/>
);
};
const App = () => {
const [data, setData] = useState(initialData);
const [loading, setLoading] = useState(false);
const loadMore = useCallback(() => {
if (loading) return;
setLoading(true);
fetchMoreData().then(newData => {
setData(prev => [...prev, ...newData]);
setLoading(false);
});
}, [loading]);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={
loading ? <ActivityIndicator /> : null
}
/>
);
};
Monitor Render Times
import { Profiler } from 'react';
const onRenderCallback = (id, phase, actualDuration) => {
console.log(`${id} (${phase}): ${actualDuration}ms`);
};
<Profiler id="FlatList" onRender={onRenderCallback}>
<FlatList data={DATA} renderItem={renderItem} />
</Profiler>
const handleScrollBeginDrag = () => {
performance.mark('scroll-start');
};
const handleScrollEndDrag = () => {
performance.mark('scroll-end');
performance.measure('scroll-duration', 'scroll-start', 'scroll-end');
const measure = performance.getEntriesByName('scroll-duration')[0];
console.log(`Scroll took ${measure.duration}ms`);
};
<FlatList
data={DATA}
renderItem={renderItem}
onScrollBeginDrag={handleScrollBeginDrag}
onScrollEndDrag={handleScrollEndDrag}
/>
Symptoms: Choppy scrolling, frame drops
Solutions:
- Implement
getItemLayout
- Reduce
windowSize
- Optimize
renderItem with React.memo
- Avoid heavy computations in render
Issue 2: Blank Content While Scrolling
Symptoms: White space during fast scrolling
Solutions:
- Increase
initialNumToRender
- Increase
maxToRenderPerBatch
- Increase
windowSize
<FlatList
initialNumToRender={20} // Increase from 10
maxToRenderPerBatch={15} // Increase from 10
windowSize={10} // Increase from 5
/>
Issue 3: Memory Issues
Symptoms: App crashes with large lists
Solutions:
- Decrease
windowSize
- Enable
removeClippedSubviews
- Optimize images (compress, use thumbnails)
- Implement pagination
Issue 4: Incorrect Item Updates
Symptoms: Wrong items update or display
Solutions:
- Use stable
keyExtractor
- Implement proper
extraData prop
const [selectedId, setSelectedId] = useState(null);
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
extraData={selectedId} // Re-render when selection changes
/>
FlatList vs SectionList
Use SectionList for grouped data:
import { SectionList } from 'react-native';
const SECTIONS = [
{ title: 'A', data: ['Alice', 'Amy'] },
{ title: 'B', data: ['Bob', 'Bill'] },
];
<SectionList
sections={SECTIONS}
renderItem={({ item }) => <Item title={item} />}
renderSectionHeader={({ section }) => (
<Text style={styles.header}>{section.title}</Text>
)}
keyExtractor={item => item}
/>
Both support the same optimization props.
Create Test Data
const generateTestData = (count) => {
return Array.from({ length: count }, (_, i) => ({
id: `item-${i}`,
title: `Item ${i}`,
description: `Description for item ${i}`,
imageUrl: `https://picsum.photos/100/100?random=${i}`,
}));
};
const TEST_DATA = generateTestData(10000);
Benchmark Configurations
const configs = [
{ name: 'Default', props: {} },
{
name: 'Optimized',
props: {
initialNumToRender: 15,
maxToRenderPerBatch: 10,
windowSize: 5,
removeClippedSubviews: true,
getItemLayout: (data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}),
},
},
];
Best Practices Checklist
Next Steps