In a React Native FlatList, how do ItemSeparatorComponent, ListHeaderComponent, ListFooterComponent and ListEmptyComponent behave?
answer
- separators only between items
- highlighted and leadingItem props
- header and footer scroll with content
- empty shows when data is empty
- component or element
basics
~20 sIn a React Native FlatList, ItemSeparatorComponent renders between items but not above the first or below the last; ListHeaderComponent and ListFooterComponent render before and after all items and scroll with them; ListEmptyComponent renders when data is empty.
solid answer
~40 sAll four slots accept a component or an element. `ItemSeparatorComponent` renders **between** items, never above the first or below the last, and receives `highlighted` and `leadingItem` props; `renderItem`'s `separators.highlight()` / `unhighlight()` toggle `highlighted` on the separators around a row, and `separators.updateProps` sets arbitrary props on the leading or trailing one. `ListHeaderComponent` and `ListFooterComponent` render before and after the items as part of the scrollable content, so they scroll away; `ListHeaderComponentStyle` and `ListFooterComponentStyle` style their wrapper views. `ListEmptyComponent` renders only when `data` has no items, and the header and footer still render around it. To centre an empty state, give the list `contentContainerStyle={{flexGrow: 1}}`. These slots are also the reason never to wrap a `FlatList` in a `ScrollView` just to add content above it.
code
tsx · 45 linesimport {FlatList, StyleSheet, Text, TextInput, View} from 'react-native';
type Contact = {id: string; name: string};
function Divider({highlighted}: {highlighted?: boolean}) {
return <View style={[styles.divider, highlighted && styles.hidden]} />;
}
export function Contacts({
contacts,
query,
onQueryChange,
}: {
contacts: Contact[];
query: string;
onQueryChange: (q: string) => void;
}) {
return (
<FlatList
data={contacts}
keyExtractor={c => c.id}
renderItem={({item}) => <Text style={styles.row}>{item.name}</Text>}
ItemSeparatorComponent={Divider}
ListHeaderComponent={
<TextInput value={query} onChangeText={onQueryChange} placeholder="Search" />
}
ListFooterComponent={<Text style={styles.footer}>{contacts.length} contacts</Text>}
ListEmptyComponent={
<View style={styles.empty}>
<Text>No contacts match "{query}"</Text>
</View>
}
contentContainerStyle={styles.content}
/>
);
}
const styles = StyleSheet.create({
content: {flexGrow: 1},
row: {padding: 12},
divider: {height: StyleSheet.hairlineWidth, backgroundColor: '#d4d4d8'},
hidden: {opacity: 0},
footer: {padding: 12, color: '#71717a'},
empty: {flex: 1, alignItems: 'center', justifyContent: 'center'},
});go deeper
Know the four slots: separators between items only, header and footer before and after the rows, and the empty component when data is empty. Each accepts a component or an element.
Explain the details: separator props highlighted and leadingItem driven by the separators object in renderItem, header and footer scrolling with the content, and flexGrow on the content container for a centred empty state.
Choose the right home for each piece of UI: fixed controls outside the list, scrolling content in header and footer, never a wrapping ScrollView. Remember slots reading outside state need extraData.
Standardise list chrome in shared components, such as dividers, empty states and footers, so every list screen gets consistent behaviour and nobody reaches for a wrapping ScrollView.
## The four slots A contacts screen needs more than rows: a divider line between contacts, a search field above them, a count or a loading note below them, and a friendly message when there are no contacts at all. `FlatList` has a slot for each. | Prop | Where it renders | Notes | |---|---|---| | `ItemSeparatorComponent` | between adjacent items | not above the first, not below the last | | `ListHeaderComponent` | before all items | scrolls with the content | | `ListFooterComponent` | after all items | scrolls with the content | | `ListEmptyComponent` | in place of items when `data` is empty | header and footer still render | Each accepts either a **component** (`ListEmptyComponent={EmptyContacts}`) or an **element** (`ListEmptyComponent={<EmptyContacts query={query} />}`). The element form is handy when the slot needs props from the screen. ## Separators The docs are precise: the separator is "rendered in between each item, but not at the top or bottom". In the implementation, the separator is attached to each cell except the last one. That means: - a list of 300 contacts renders 299 separators; - a top or bottom border has to come from the header, the footer or the list's content container style; - with `numColumns` above 1, separators go between **rows**, not between items in the same row. The separator component receives two props by default: - **`highlighted`** — toggled by `separators.highlight()` and `separators.unhighlight()`, which `renderItem` receives in its argument alongside `item` and `index`; the call updates the separators around that row, for example to hide the dividers while a row is pressed; - **`leadingItem`** — the item above the separator, useful for indenting a divider differently after certain rows. For anything else, `separators.updateProps('leading' | 'trailing', newProps)` merges custom props into the separator before or after the row. ## Header and footer The header and footer are rendered as the first and last cells of the list's content: - they **scroll with the rows**; a fixed search bar that must never scroll away belongs outside the list, above it in the screen layout; - `ListHeaderComponentStyle` and `ListFooterComponentStyle` style the wrapper view around each; - they are the correct place for content that would otherwise tempt you to nest the list inside a `ScrollView`, which React Native reports as an error because it can break windowing. ## The empty state `ListEmptyComponent` renders when the list has zero items. The header and footer still render around it, so a search field in the header stays usable when a query matches nothing. Centring the message takes one extra step. The empty component is a child of the list's content container, which is only as tall as its content. Setting `contentContainerStyle={{flexGrow: 1}}` on the list lets the content container fill the viewport, and the empty component can then use `flex: 1` and `justifyContent: 'center'`. ## Re-rendering the slots `FlatList` is a pure component. If the header, footer or separator reads state that is not in `data` — the current query, a selection — and the list's props do not otherwise change, the slot will not update. Pass such values through `extraData`, or pass the slot as an element built from the current state so the prop itself changes. ## Common mistakes - Expecting a separator above the first row. - Putting a sticky search bar in `ListHeaderComponent` and watching it scroll away. - Wrapping the list in a `ScrollView` to get a header. - Rendering the empty state conditionally outside the list, losing the header and footer.
- How do you draw a line above the first contact and below the last one?`ItemSeparatorComponent` never renders at the top or bottom, so add those borders elsewhere: a bottom border on the header, a top border on the footer, or a border on `contentContainerStyle`. Adding them to every row instead would double up with the separators.
- Why does the search field in ListHeaderComponent disappear when the user scrolls?The header is the first cell of the scrollable content, so it scrolls away with the rows. A search bar that must stay visible belongs outside the list, above it in the screen layout; the header slot is for content that should scroll with the list.
saying these in an interview costs you the question
- ItemSeparatorComponent also renders above the first and below the last item.
- ListEmptyComponent replaces the header and footer when data is empty.
- ListHeaderComponent stays fixed at the top while rows scroll.
- Wrapping FlatList in a ScrollView is the normal way to add a header.
- The empty component centres itself without any content container style.