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 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.
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
The array of items to render
Function that returns JSX for each item. Receives {item, index, separators} as argument.
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:
/**
* 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:
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 ;
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 }
/>
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:
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:
/**
* 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 }
/>
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
Always provide keyExtractor
Use PureComponent or React.memo for list items
Prevent unnecessary re-renders of list items when parent updates.
Provide getItemLayout for fixed-height items
This skips measurement and improves scroll performance significantly.
Use removeClippedSubviews on Android
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