skip to content

In a React Native FlashList grid mixing listing cards and sponsored banners, what does getItemType do, and why does it matter?

level: middleimportance: should knowfreq 28%

answer

  1. one recycle pool per type
  2. default type is 0
  3. banner reused as card means rebuild
  4. called very often, keep it cheap
  5. renderItem still picks the component

basics

~20 s

FlashList's getItemType(item, index) labels each item with a type, and cells are recycled only between items of the same type. Without it, a sponsored-banner cell can be reused for a listing card, forcing React to rebuild its subtree.

solid answer

~40 s

FlashList recycles cells from pools, and `getItemType(item, index, extraData)` decides which pool: it returns a string or number, and a cell is reused only for another item of the **same type**. Every item is type `0` by default, so a grid mixing listing cards and sponsored banners shares one pool. Then a cell that rendered a banner can be handed a listing card: React cannot update the banner's tree into a card, so it unmounts the banner's children and mounts the card's - most of the reuse benefit gone, plus a layout change. With `getItemType={item => item.kind}`, banners recycle into banners and cards into cards. `renderItem` still chooses the component; the type only routes recycling. The docs warn the callback is called very frequently, so it should just read a field.

code

tsx · 29 lines
tsx
import { Text } from 'react-native';
import { FlashList } from '@shopify/flash-list';

type Row =
  | { kind: 'listing'; id: string; title: string; price: string }
  | { kind: 'banner'; id: string; headline: string };

export function MarketplaceGrid({ rows }: { rows: Row[] }) {
  return (
    <FlashList
      data={rows}
      numColumns={2}
      keyExtractor={row => row.id}
      getItemType={row => row.kind} // separate recycle pools per row shape
      overrideItemLayout={(layout, row) => {
        if (row.kind === 'banner') layout.span = 2; // banners fill the row
      }}
      renderItem={({ item }) =>
        item.kind === 'banner' ? (
          <Text>{item.headline}</Text>
        ) : (
          <Text>
            {item.title} - {item.price}
          </Text>
        )
      }
    />
  );
}

go deeper

for a junior

Remember that getItemType tells FlashList which rows look alike, so it reuses cells only between rows of the same kind.

for a middle

Explain per-type recycle pools, the default type, and why reusing a banner cell as a listing card costs almost a full mount.

for a senior

Choose types by structural difference, keep the callback cheap and stable, and separate type routing from state resets and key problems when a mixed grid janks.

for a principal

Shape feed and grid payloads so row kinds are explicit fields, making recycling types, analytics and rendering all read the same discriminator.

## What recycling reuses FlashList keeps cells mounted after they scroll away and reuses them for new items by re-rendering the same component tree with a new `item`. That is cheap when the new item renders the **same shape** of tree - React updates text, image sources and styles in place. It is expensive when the new item needs a different tree: React's reconciliation replaces subtrees whose component types differ, so the old children unmount and new ones mount. A marketplace grid shows why this matters. Most rows are **listing cards** (photo, title, price, heart), but every tenth row is a **sponsored banner** (full-width image, call-to-action button, disclosure text). The two share almost no structure. ## What getItemType does `getItemType` is a FlashList prop: ```tsx getItemType?: (item: T, index: number, extraData?: any) => string | number | undefined; ``` - It assigns every item a **type**; the default type is `0`, and returning `undefined` keeps the default for that index. - FlashList keeps **one recycle pool per type** and reuses a cell only for an item of the same type. - It does **not** choose what to render - `renderItem` still switches on the item and returns the right component. | | Without `getItemType` | With `getItemType={item => item.kind}` | |---|---|---| | Pools | one shared pool | one for `'listing'`, one for `'banner'` | | Banner cell reused for a card | yes - banner subtree unmounted, card subtree mounted | no - it waits for another banner | | Layout after reuse | cell size changes, list re-lays out | cell keeps a similar size | | Cost per reuse | close to a fresh mount | a props update | ## Rules for the callback 1. **Keep it cheap.** The FlashList docs warn it is called very frequently. Read a field that is already on the item; do not compute, look up or allocate. 2. **Derive it from the item.** Make the type a pure function of the item's own fields, so the same item always lands in the same pool. 3. **Use few types.** Each type is its own pool. A handful of genuinely different row shapes is right; one type per item defeats pooling. With a very large number of types, `maxItemsInRecyclePool` caps how many idle cells are kept (there is no limit by default). 4. **Type what differs structurally.** Two listing cards with different photos are the same type; a card and a banner are not. A card with an optional "sold" badge can stay the same type if the badge is a small conditional child. ## Other places types help - **Headers inside horizontal lists.** The FlashList known-issues page suggests rendering a header as the first item with its own type when the list must size itself to its children. - **Section-like lists.** Section header rows and item rows are structurally different and belong in different pools. - **Sticky headers.** `renderItem` receives a `target` of `'StickyHeader'` when it renders an item as the sticky header, alongside `'Cell'` and `'Measurement'`. ## Diagnosing a janky mixed grid 1. Profile in a **release build**; development mode exaggerates every render. 2. Check whether `getItemType` is set and whether its values match the shapes `renderItem` returns. 3. Search the row components for `key` props that change with the item. 4. Look for expensive work in `renderItem` or the row body that now runs on every reuse. 5. Check that props passed to the list are memoized, which v2 relies on. ## How it fits the rest of recycling `getItemType` reduces rebuilds between shapes; it does nothing about **state** carried from one item to the next of the same type. A listing card's local state still leaks from listing to listing unless it is derived from data or reset with `useRecyclingState`. Likewise, a `key` prop inside the row still forces a remount regardless of type. The three concerns - type routing, state reset and keys - are separate, and a smooth heterogeneous grid needs all three right.

  • Does getItemType decide which component renderItem renders?
    No. `renderItem` still inspects the item and returns the component. `getItemType` only tells FlashList which recycle pool a cell belongs to, so a cell is reused for an item that renders the same kind of tree. If the two disagree - a type that does not match what `renderItem` returns - you get the rebuild cost back.
  • Why not return item.id from getItemType to be safe?
    Because every item would get its own pool, and a cell could only be reused for the very same item. Recycling would stop working: scrolling to new items would always mount new cells, and each pool would hold idle cells nobody can use. Types should name a handful of structurally different row shapes.

saying these in an interview costs you the question

  • getItemType chooses which component FlashList renders for each item.
  • Returning a unique id per item from getItemType gives the best recycling.
  • Without getItemType, FlashList never reuses cells across different row kinds.
  • getItemType runs once per item, so it can do expensive lookups.
  • getItemType also resets the local state of recycled cells.