skip to content

FlatList Props & Keys

FlatList renders a flat array through renderItem and keyExtractor, with header, footer, empty and separator slots. Interviewers probe stable keys and why rows ignore state changes without extraData.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a React Native FlatList, what does keyExtractor do, and which key does the list use when you omit it?

level: juniorimportance: must knowfreq 72%

answer

  1. one string per item
  2. React key and cache key
  3. item.key, then item.id
  4. index as the last resort
  5. missing-keys warning

basics

~20 s

keyExtractor returns a unique, stable string for each FlatList item; the list uses it as the React key and to cache that cell's measurements. Without it, FlatList tries item.key, then item.id, then falls back to the index and warns about missing keys.

solid answer

~50 s

`keyExtractor(item, index)` returns the string that identifies an item. React Native's `FlatList` uses it twice: as the React `key` of the row's cell, and as the key under which the list caches that cell's measured layout and refs. If you omit it, the default extractor returns `item.key` if present, otherwise `item.id`, otherwise `String(index)` — and in that last case the list logs `VirtualizedList: missing keys for items…`. For a contacts list, `keyExtractor={c => String(c.id)}` is the usual answer: the custom extractor is typed to return a string, so numeric ids are converted. Index keys are wrong as soon as the data can be filtered, sorted or prepended, because a key then points at a different contact and both row state and cached measurements follow the wrong item. Keys must be unique within the list and stable across renders, never random.

code

tsx · 14 lines
tsx
import {FlatList, Text} from 'react-native';

type Contact = {contactId: number; name: string; phone: string};

export function ContactList({contacts}: {contacts: Contact[]}) {
  return (
    <FlatList
      data={contacts}
      // No `key` or `id` field, so the default would fall back to indices.
      keyExtractor={contact => String(contact.contactId)}
      renderItem={({item}) => <Text>{item.name}</Text>}
    />
  );
}

go deeper

for a junior

Know the signature and the default order: item.key, then item.id, then the index with a missing-keys warning. Return a stable string such as String(contact.id).

for a middle

Explain the two jobs of the key in FlatList: React identity for the cell and the key of the list's measurement cache. Show why index keys go wrong after filtering or prepending.

for a senior

Diagnose rows that show another item's state or jumpy offsets after a filter by checking key stability, including composite keys when ids are only unique per type or per page.

for a principal

Make stable item identity part of the data contract with the backend, so every list in the app can key by it instead of inventing keys screen by screen.

## What keyExtractor is for A `FlatList` renders a flat array (`data`) through a render function (`renderItem`). Each rendered row lives inside a cell that the list has to identify across renders, across scrolling and across data changes. `keyExtractor` supplies that identity: ```tsx keyExtractor={(item, index) => string} ``` The docs describe the returned key as being "used for caching and as the react key to track item re-ordering". In React Native's implementation, the key is used in several places: - as the **React `key`** of the cell component, so React matches the row to the right item when the array changes; - as the key of the list's **cell metrics cache** — the measured position and size of each rendered cell, which the list uses to decide what to render and where to scroll; - as the key for the list's **cell refs** and the index-to-key map it keeps for the rendered window. ## The default extractor When you do not pass `keyExtractor`, React Native uses a default that checks, in order: 1. `item.key`, if the item is an object with a non-null `key`; 2. `item.id`, if the item is an object with a non-null `id`; 3. `String(index)` otherwise. When the list falls back to the index, it logs a one-time warning: `VirtualizedList: missing keys for items, make sure to specify a key or id property on each item or provide a custom keyExtractor.` Data shaped with an `id` field therefore works without a custom extractor; data with `contactId` or `uuid` fields falls through to indices. ## What makes a good key | Choice | Verdict | Why | |---|---|---| | `String(contact.id)` from the backend | good | unique and stable for the item's lifetime | | a composite such as `` `${type}-${id}` `` | good | needed when ids are only unique per type | | array index | risky | identity shifts on filter, sort or insert | | `Math.random()` or a fresh UUID per render | wrong | every render looks like a new list | | a display name | risky | two contacts can share a name | The custom extractor's TypeScript signature returns a `string`, so convert numeric ids explicitly. ## Why index keys break a contacts list Imagine a 300-row contacts list with a search box. The user types "an" and the data shrinks to twelve contacts. With index keys, the cell that was key `"3"` (Andrea) is now key `"3"` for a different person: - **Row state follows the key, not the contact.** An expanded row, a text input or a pending image load stays attached to the position. - **Cached measurements follow the key.** The list's metrics cache still holds the old height for key `"3"`, so scroll offsets and the rendered window can be computed from the wrong row until it is measured again. - **Separators** that were highlighted for a leading item follow the wrong row. With `String(contact.id)`, each cell keeps its identity no matter where the contact moves in the array. ## With multiple columns When `numColumns` is greater than 1, `FlatList` groups items into rows, and the key of a row is the keys of its items joined with `:`. Your `keyExtractor` still receives single items, and uniqueness per item is still what matters. ## Common mistakes - Relying on the default with data that has no `key` or `id` field, and ignoring the missing-keys warning. - Returning a number from a custom extractor in TypeScript code instead of a string. - Using keys that are unique per page of results but collide once pages are merged. - Generating keys in `renderItem` instead of `keyExtractor`; the list's caches never see them.

  • Does a FlatList need keyExtractor when every item has an id field?
    Not strictly. The default extractor checks `item.key`, then `item.id`, so objects with an `id` are keyed by it. Many teams still pass an explicit `keyExtractor` so that a later rename of the field cannot silently push the list back to index keys, and so the key is visibly a string, for example `String(item.id)`.
  • Why is a key generated with Math.random() inside keyExtractor worse than an index key?
    Every render produces new keys, so every cell looks new: React remounts every visible row, row state is lost on each render, and the list's measurement cache never gets a hit. An index key is at least stable while the array does not change shape.
  • What happens to the list's cached measurements when keys shift after a filter?
    The metrics cache is keyed by the cell key, so a shifted key inherits the old item's measured size and offset. Until the new row is measured, the list can compute the rendered window and scroll targets from the wrong height, which shows up as jumps or brief gaps.

saying these in an interview costs you the question

  • FlatList ignores keys because it virtualizes rows anyway.
  • Without keyExtractor, FlatList always uses the array index.
  • Index keys are fine for a list that can be searched or sorted.
  • A random UUID per render is the safest unique key.
  • keyExtractor only matters for React, not for the list's own caching.
open as a page

In a React Native FlatList, why can rows ignore a state change such as a selected contact, and what does extraData fix?

level: middleimportance: must knowfreq 58%

basics

~20 s

FlatList is a PureComponent, so it skips re-rendering when its props are shallowly equal. If renderItem depends on state outside data, such as a selected id, pass that state as extraData so the list re-renders and rows see the change.

open as a page

In a React Native FlatList, how do ItemSeparatorComponent, ListHeaderComponent, ListFooterComponent and ListEmptyComponent behave?

level: juniorimportance: should knowfreq 48%

basics

~20 s

In 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.

open as a page

In a React Native FlatList, how does numColumns lay out a grid, and why does changing it at runtime throw an error?

level: middleimportance: should knowfreq 38%

basics

~20 s

With numColumns above 1, a React Native FlatList groups items into row views of that many items, filling rows left to right. It cannot be combined with horizontal, and changing numColumns after mount throws; change the list's key to remount it instead.

open as a page

Why can scrollToIndex on a React Native FlatList throw or fail for a far-away row, and how do you make it reliable?

level: seniorimportance: should knowfreq 35%

basics

~20 s

FlatList only knows the offsets of rows it has measured. scrollToIndex to an unmeasured row throws unless getItemLayout or onScrollToIndexFailed is set; the failure callback lets you scroll near the row and retry once it renders.

open as a page