Skip to main content

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:
  1. Render only visible items: Items outside viewport are unmounted
  2. Recycle components: Reuse components as you scroll
  3. Batch updates: Group multiple updates for efficiency
  4. 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.

2. Use keyExtractor Correctly

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}
    />
  );
};

4. Configure Window Size

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} />
      }
    />
  );
};

Infinite Scroll

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
      }
    />
  );
};

Measuring Performance

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>

Track Scroll Performance

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}
/>

Common Performance Issues

Issue 1: Slow Scrolling

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.

Testing Performance

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

  • Use stable keyExtractor (not index)
  • Implement getItemLayout for fixed-height items
  • Wrap items with React.memo
  • Use useCallback for event handlers
  • Configure initialNumToRender, maxToRenderPerBatch, windowSize
  • Enable removeClippedSubviews on Android
  • Optimize images (size, caching, loading)
  • Avoid anonymous functions in props
  • Profile and measure performance
  • Test on low-end devices

Next Steps

Build docs developers (and LLMs) love