In a React Native FlatList, how does numColumns lay out a grid, and why does changing it at runtime throw an error?
answer
- items grouped into row views
- zig-zag like flexWrap
- not with horizontal
- changing it on the fly throws
- change the key to remount
basics
~20 sWith 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.
solid answer
~40 s`numColumns` makes `FlatList` group consecutive items into rows: the list virtualizes **rows**, each rendered as a `View` with `flexDirection: 'row'` styled by `columnWrapperStyle`, and your `renderItem` still receives single items with their real index. The layout zig-zags like `flexWrap`, items should share a height (masonry is not supported), and it only works vertically — with `horizontal` it throws `numColumns does not support horizontal.` Changing `numColumns` on a mounted list throws `Changing numColumns on the fly is not supported…`; the fix the message suggests is to change the list's `key` so React mounts a fresh list, e.g. `key={`cols-${columns}`}` when rotating a tablet. Two more traps: the last row may be partly filled, so items sized with `flex: 1` stretch there; and `scrollToIndex` counts rows, not items, when there are several columns.
code
tsx · 31 linesimport {FlatList, StyleSheet, Text, View, useWindowDimensions} from 'react-native';
type Contact = {id: string; name: string};
export function ContactGrid({contacts}: {contacts: Contact[]}) {
const {width} = useWindowDimensions();
const columns = width >= 768 ? 3 : 1;
const itemWidth = (width - 16 * (columns + 1)) / columns;
return (
<FlatList
key={`contacts-${columns}`} // new key = fresh list when columns change
data={contacts}
numColumns={columns}
keyExtractor={c => c.id}
columnWrapperStyle={columns > 1 ? styles.row : undefined}
contentContainerStyle={styles.content}
renderItem={({item}) => (
<View style={[styles.card, {width: itemWidth}]}>
<Text>{item.name}</Text>
</View>
)}
/>
);
}
const styles = StyleSheet.create({
content: {padding: 16, gap: 16},
row: {gap: 16},
card: {padding: 12, borderWidth: 1, borderColor: '#d4d4d8'},
});go deeper
Know that numColumns turns a FlatList into a vertical grid, that it cannot be horizontal, and that items should share a height.
Explain how the list groups items into row views styled by columnWrapperStyle, why changing numColumns throws, and how a key change remounts the list.
Handle production details: rotation-driven column counts, a stretched last row, separators between rows, and scrollToIndex counting rows in a multi-column list.
Decide when a FlatList grid is enough and when variable-height or horizontal grids justify a different component, weighing the remount cost of changing columns.
## What numColumns does `FlatList` normally renders one item per cell. With `numColumns` greater than 1, it groups consecutive items into **rows** of that many items and virtualizes the rows instead: 1. the list tells `VirtualizedList` it has `ceil(data.length / numColumns)` cells; 2. each cell's item is an array of up to `numColumns` items; 3. the row is rendered as a `View` with `flexDirection: 'row'`, composed with your `columnWrapperStyle`; 4. inside the row, your `renderItem` is called once per item, with that item's **real index** in `data`. The docs describe the result as a zig-zag, "like a `flexWrap` layout", and add that "items should all be the same height — masonry layouts are not supported". A row is as tall as its tallest item. A contacts screen on a tablet might show contacts as a three-column grid of cards; on a phone, one column. ## The constraints the list enforces | Situation | What happens | |---|---| | `numColumns > 1` with `horizontal` | invariant: `numColumns does not support horizontal.` | | `columnWrapperStyle` with one column | invariant: `columnWrapperStyle not supported for single column lists` | | `numColumns` changes after mount | invariant: `Changing numColumns on the fly is not supported. Change the key prop on FlatList when changing the number of columns to force a fresh render of the component.` | The last one is checked when the list updates, so it appears the moment you rotate a device and recompute the column count from the window width. ## Changing the column count safely The error message gives the fix: **change the `key` prop of the `FlatList`** whenever the column count changes. A new key makes React unmount the old list and mount a new one, which rebuilds the row grouping, the measurement cache and the rendered window from scratch. - Derive the key from the column count: `` key={`contacts-${columns}`} ``. - Expect the new list to start at the top; save and restore a position yourself if it matters. - Keep the column count stable while the list is mounted for any other reason. ## Sizing items in a grid - **Item width.** Either give items `flex: 1` so they share the row, or compute a fixed width from the window width. With `flex: 1`, a partly filled last row — say 301 contacts in three columns — stretches its lone item across the full width. Fixed widths, or padding the data with invisible filler items, avoid that. - **Gaps.** `columnWrapperStyle` is the place for spacing between items in a row, for example `gap` or `justifyContent`. - **Separators.** `ItemSeparatorComponent` renders between **rows**, not between items in the same row. ## Keys and scrolling with columns - Your `keyExtractor` still receives single items. Internally, the row's key is the keys of its items joined with `:`, so per-item uniqueness still matters. - `scrollToIndex` is forwarded to the underlying list, which counts **rows** when there are several columns. To scroll to item 90 in a three-column grid, pass the row index, `Math.floor(90 / 3)`. - The index passed to `renderItem` is the item's real index, not the row's. ## Why not horizontal grids? The grouping always builds horizontal rows inside a vertically scrolling list. A horizontally scrolling grid needs a different structure, for example a horizontal list whose items are columns you build yourself. ## Common mistakes - Computing `numColumns` from `useWindowDimensions` without changing the key, and crashing on rotation. - Using `flex: 1` items and shipping a stretched last card. - Passing an item index to `scrollToIndex` in a multi-column list. - Setting `columnWrapperStyle` on a list that sometimes has one column.
- Why does the example pass columnWrapperStyle only when there is more than one column?`FlatList` throws `columnWrapperStyle not supported for single column lists` when the style is set while `numColumns` is 1. On a phone the grid collapses to one column, so the style must be omitted there.
- How do you scroll a three-column FlatList to item 90?With several columns the list virtualizes rows, and `scrollToIndex` counts rows, so pass `Math.floor(90 / 3)`, which is row 30. Passing 90 would target row 90, which is item 270 or an out-of-range error.
saying these in an interview costs you the question
- numColumns works with horizontal to build a sideways grid.
- numColumns can change freely on a mounted list, for example on rotation.
- numColumns supports masonry layouts with items of different heights.
- ItemSeparatorComponent renders between every item in a row.
- scrollToIndex always counts items, whatever numColumns is.