Skip to main content
React Native provides specialized components for rendering lists of data efficiently. Unlike using map() with ScrollView, these components only render items currently visible on screen.

FlatList vs ScrollView

FlatList

Lazy-loads items as you scroll. Best for large datasets. Only renders visible items.

ScrollView

Renders all children at once. Good for small, static lists. Simple but not performant for large lists.
Don’t use ScrollView with map() for large lists. Use FlatList instead to avoid performance issues.

Basic FlatList

The FlatList component requires two props: data and renderItem.
FlatList Example
import {FlatList, View, Text, StyleSheet} from 'react-native';

function MyList() {
  const data = [
    {id: '1', title: 'First Item'},
    {id: '2', title: 'Second Item'},
    {id: '3', title: 'Third Item'},
  ];

  const renderItem = ({item}) => (
    <View style={styles.item}>
      <Text style={styles.title}>{item.title}</Text>
    </View>
  );

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={item => item.id}
    />
  );
}

const styles = StyleSheet.create({
  item: {
    backgroundColor: '#f9f9f9',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 16,
  },
});

FlatList Props

Essential Props

data
Array
required
The array of items to render
renderItem
Function
required
Function that returns JSX for each item. Receives {item, index, separators} as argument.
keyExtractor
Function
Function that returns a unique key for each item. Default uses item.key or falls back to index.

From React Native Source

From Libraries/Lists/FlatList.js:
FlatList.js (source)
/**
 * Takes an item from `data` and renders it into the list. Example usage:
 *
 * <FlatList
 *   data={[{title: 'Title Text', key: 'item1'}]}
 *   renderItem={({item, separators}) => (
 *     <TouchableHighlight
 *       onPress={() => this._onPress(item)}
 *       onShowUnderlay={separators.highlight}
 *       onHideUnderlay={separators.unhighlight}>
 *       <View style={{backgroundColor: 'white'}}>
 *         <Text>{item.title}</Text>
 *       </View>
 *     </TouchableHighlight>
 *   )}
 * />
 */

Real-World Example

A contact list with touchable items:
Contact List
import {FlatList, View, Text, TouchableOpacity, StyleSheet} from 'react-native';
import {useState} from 'react';

function ContactList() {
  const [contacts] = useState([
    {id: '1', name: 'Alice Johnson', phone: '+1 234 567 8900'},
    {id: '2', name: 'Bob Smith', phone: '+1 234 567 8901'},
    {id: '3', name: 'Charlie Brown', phone: '+1 234 567 8902'},
    {id: '4', name: 'Diana Prince', phone: '+1 234 567 8903'},
    {id: '5', name: 'Ethan Hunt', phone: '+1 234 567 8904'},
  ]);

  const renderItem = ({item}) => (
    <TouchableOpacity
      style={styles.item}
      onPress={() => console.log('Selected:', item.name)}
    >
      <View>
        <Text style={styles.name}>{item.name}</Text>
        <Text style={styles.phone}>{item.phone}</Text>
      </View>
    </TouchableOpacity>
  );

  return (
    <FlatList
      data={contacts}
      renderItem={renderItem}
      keyExtractor={item => item.id}
      ItemSeparatorComponent={() => <View style={styles.separator} />}
    />
  );
}

const styles = StyleSheet.create({
  item: {
    padding: 16,
    backgroundColor: 'white',
  },
  name: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  phone: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
  separator: {
    height: 1,
    backgroundColor: '#e0e0e0',
  },
});

export default ContactList;

Performance Optimization

getItemLayout

If your items have fixed height, provide getItemLayout for better performance:
const ITEM_HEIGHT = 80;

<FlatList
  data={data}
  renderItem={renderItem}
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
  keyExtractor={item => item.id}
/>
From the React Native source: “getItemLayout is an optional optimization that lets us skip measurement of dynamic content if you know the height of items a priori.”

initialNumToRender

Control how many items render initially:
<FlatList
  data={largeDataset}
  renderItem={renderItem}
  initialNumToRender={10}
  maxToRenderPerBatch={10}
  windowSize={10}
/>

Headers, Footers, and Empty States

import {FlatList, View, Text, ActivityIndicator} from 'react-native';

function MyList({data, loading}) {
  return (
    <FlatList
      data={data}
      renderItem={({item}) => <Text>{item.name}</Text>}
      ListHeaderComponent={() => (
        <View style={{padding: 16, backgroundColor: '#f0f0f0'}}>
          <Text style={{fontSize: 20, fontWeight: 'bold'}}>My Contacts</Text>
        </View>
      )}
      ListFooterComponent={() => (
        loading ? <ActivityIndicator /> : null
      )}
      ListEmptyComponent={() => (
        <View style={{padding: 20, alignItems: 'center'}}>
          <Text>No contacts found</Text>
        </View>
      )}
      keyExtractor={item => item.id}
    />
  );
}

Pull to Refresh

import {FlatList, Text, RefreshControl} from 'react-native';
import {useState} from 'react';

function RefreshableList() {
  const [data, setData] = useState([]);
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = async () => {
    setRefreshing(true);
    try {
      const response = await fetch('https://api.example.com/data');
      const newData = await response.json();
      setData(newData);
    } finally {
      setRefreshing(false);
    }
  };

  return (
    <FlatList
      data={data}
      renderItem={({item}) => <Text>{item.title}</Text>}
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
      }
      keyExtractor={item => item.id}
    />
  );
}

SectionList

For lists with section headers, use SectionList:
SectionList Example
import {SectionList, View, Text, StyleSheet} from 'react-native';

function ContactsByLetter() {
  const sections = [
    {
      title: 'A',
      data: ['Alice', 'Amy', 'Aaron'],
    },
    {
      title: 'B',
      data: ['Bob', 'Betty', 'Brian'],
    },
    {
      title: 'C',
      data: ['Charlie', 'Carol', 'Chris'],
    },
  ];

  return (
    <SectionList
      sections={sections}
      renderItem={({item}) => (
        <View style={styles.item}>
          <Text>{item}</Text>
        </View>
      )}
      renderSectionHeader={({section}) => (
        <View style={styles.sectionHeader}>
          <Text style={styles.sectionTitle}>{section.title}</Text>
        </View>
      )}
      keyExtractor={(item, index) => item + index}
    />
  );
}

const styles = StyleSheet.create({
  sectionHeader: {
    backgroundColor: '#f4f4f4',
    padding: 8,
    paddingLeft: 16,
  },
  sectionTitle: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#666',
  },
  item: {
    padding: 16,
    backgroundColor: 'white',
  },
});

SectionList from Source

From Libraries/Lists/SectionList.js:
SectionList.js (source)
/**
 * The actual data to render, akin to the `data` prop in [`<FlatList>`].
 *
 * General shape:
 *
 *     sections: $ReadOnlyArray<{
 *       data: $ReadOnlyArray<SectionItem>,
 *       renderItem?: ({item: SectionItem, ...}) => ?React.MixedElement,
 *       ItemSeparatorComponent?: ?ReactClass<{highlighted: boolean, ...}>,
 *     }>
 */

Horizontal Lists

<FlatList
  data={photos}
  renderItem={({item}) => (
    <Image
      source={{uri: item.url}}
      style={{width: 200, height: 200, marginRight: 10}}
    />
  )}
  horizontal
  showsHorizontalScrollIndicator={false}
  keyExtractor={item => item.id}
/>

Multiple Columns

<FlatList
  data={items}
  renderItem={({item}) => (
    <View style={{flex: 1, margin: 4}}>
      <Text>{item.name}</Text>
    </View>
  )}
  numColumns={2}
  keyExtractor={item => item.id}
/>

Infinite Scroll

import {FlatList, Text, ActivityIndicator} from 'react-native';
import {useState} from 'react';

function InfiniteList() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [page, setPage] = useState(1);

  const loadMore = async () => {
    if (loading) return;

    setLoading(true);
    try {
      const response = await fetch(`https://api.example.com/items?page=${page}`);
      const newItems = await response.json();
      setData([...data, ...newItems]);
      setPage(page + 1);
    } finally {
      setLoading(false);
    }
  };

  return (
    <FlatList
      data={data}
      renderItem={({item}) => <Text>{item.title}</Text>}
      onEndReached={loadMore}
      onEndReachedThreshold={0.5}
      ListFooterComponent={() => (
        loading ? <ActivityIndicator size="large" /> : null
      )}
      keyExtractor={item => item.id}
    />
  );
}

Best Practices

Keys help React identify which items have changed, are added, or removed. Never use array index as key for dynamic lists.
Prevent unnecessary re-renders of list items when parent updates.
This skips measurement and improves scroll performance significantly.
Unmount components that are off screen to reduce memory usage.
Use appropriate image sizes and consider using FastImage for better performance.

Next Steps

Networking

Learn to fetch data for your lists from APIs

Platform-Specific Code

Handle list behavior differences between iOS and Android

Build docs developers (and LLMs) love