Rendering long lists is where mobile performance is won or lost. Use ScrollView for small content and FlatList for data — it only renders what is on screen.
Why: a View does not scroll. ScrollView makes its children scrollable and is fine for a SMALL, known amount of content — a settings screen, a form. The catch: it renders everything at once, so it is the wrong tool for long or unbounded lists.
import { ScrollView, Text } from 'react-native'
function Settings() {
return (
<ScrollView contentContainerStyle={{ padding: 16, gap: 12 }}>
<Text>Row one</Text>
<Text>Row two</Text>
{/* fine for a handful of items */}
</ScrollView>
)
}Why: FlatList is built for long lists — it only renders the items currently on screen and recycles them as you scroll, so a list of thousands stays smooth. You give it data, a renderItem function, and a keyExtractor for stable keys.
import { FlatList, Text, View } from 'react-native'
const users = [
{ id: '1', name: 'Ada' },
{ id: '2', name: 'Grace' },
]
function UserList() {
return (
<FlatList
data={users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={{ padding: 16 }}>
<Text>{item.name}</Text>
</View>
)}
/>
)
}Why: FlatList has props for the surrounding chrome so you do not hand-build it. ItemSeparatorComponent draws between rows, ListHeaderComponent/ListFooterComponent add fixed ends, and ListEmptyComponent shows when data is empty — a polished list without extra layout code.
<FlatList
data={users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.name}</Text>}
ItemSeparatorComponent={() => (
<View style={{ height: 1, backgroundColor: '#eee' }} />
)}
ListEmptyComponent={<Text>No users yet</Text>}
/>Why: onRefresh + refreshing add native pull-to-refresh to a FlatList. When your data is grouped (contacts by letter, items by category), SectionList is the sibling that renders section headers. Both share the same windowing performance.
function Feed() {
const [refreshing, setRefreshing] = useState(false)
const reload = () => {
setRefreshing(true)
// ...fetch new data, then:
setRefreshing(false)
}
return (
<FlatList
data={users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.name}</Text>}
refreshing={refreshing}
onRefresh={reload}
/>
)
}