skip to content

In a FlashList v2 marketplace grid, how do you make a featured listing span two columns, and when do you need masonry instead?

level: middleimportance: nice to knowfreq 18%

answer

  1. mutate layout.span, do not return
  2. span only, no sizes in v2
  3. grid rows match the tallest cell
  4. masonry: columns grow independently
  5. shortest column changes visual order

basics

~20 s

In FlashList v2, overrideItemLayout sets layout.span for an item, so a featured listing can fill two columns of a numColumns grid. Use the masonry prop when cells have different heights and columns should grow independently instead of in aligned rows.

solid answer

~40 s

A FlashList grid is `numColumns` wide and lays items out row by row. To widen one item, pass `overrideItemLayout={(layout, item) => { if (item.featured) layout.span = 2; }}` - you **mutate** the `layout` object rather than return anything, and in v2 only `span` is read; v1's size estimates are ignored. The callback is called very often, so keep it to a field check. In a plain grid, v2 makes side-by-side items match the tallest one in the row. When photos have varying aspect ratios and equal-height rows would waste space, add the `masonry` prop: columns stack independently. By default `optimizeItemArrangement` is on, placing single-column items into the shortest column, so the visual order can differ from data order; set it to `false` to place items sequentially.

code

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

type Listing = { id: string; title: string; featured: boolean };

export function ListingGrid({ listings }: { listings: Listing[] }) {
  return (
    <FlashList
      data={listings}
      numColumns={2}
      keyExtractor={listing => listing.id}
      overrideItemLayout={(layout, listing, _index, maxColumns) => {
        // Mutate; the return value is ignored. v2 reads only span.
        if (listing.featured) layout.span = maxColumns;
      }}
      renderItem={({ item }) => <Text>{item.title}</Text>}
    />
  );
}

go deeper

for a junior

Recall that numColumns makes a grid, overrideItemLayout sets span for wide items, and masonry is a prop in FlashList v2.

for a middle

Explain mutating layout.span, why v2 ignores sizes, how grid rows align to the tallest cell, and how masonry differs.

for a senior

Choose grid or masonry from content and sort requirements, handle optimizeItemArrangement's order trade-off, and pair full-width spans with item types.

for a principal

Agree with design on which layout rules the catalogue must keep, such as strict sort order, so list engineering choices are made against those rules.

## Grids in FlashList v2 FlashList turns into a grid with `numColumns`. Items are placed row by row, left to right, wrapping like a `flexWrap` layout. Two v2 behaviours matter for a marketplace grid: - **Rows align.** If side-by-side items have different heights, v2 makes the shorter ones match the tallest item in the row - something v1 could not do. - **Spans are allowed.** An item can occupy more than one column, set through `overrideItemLayout`. ## overrideItemLayout The signature is: ```tsx overrideItemLayout?: ( layout: { span?: number }, item: T, index: number, maxColumns: number, extraData?: any ) => void; ``` Key points, all from the FlashList v2 docs and source: 1. **Mutate, do not return.** Set `layout.span`; the return value is ignored, and anything you leave unset falls back to the default of one column. 2. **Span only.** v1 also let you set an estimated size here; v2 reads only `span`, because it measures real sizes. 3. **Width follows span.** A cell's width is the list width divided by `numColumns`, times its span. 4. **Keep it fast.** The docs warn it is called very frequently; read a field such as `item.featured`, and use `maxColumns` rather than hard-coding the column count if the grid can change. If a two-column featured listing lands where only one column is left in the current row, the layout moves it to the start of the next row, which leaves a gap. Designers often place featured items at positions that fall on row starts for that reason. ## Masonry A **masonry** layout drops row alignment: each column grows on its own, so tall and short cells pack without gaps. In v2 it is a prop on `FlashList`, replacing v1's `MasonryFlashList` component: | | Grid (`numColumns`) | Masonry (`masonry` + `numColumns`) | |---|---|---| | Row alignment | rows align; shorter cells stretch to the tallest | none; columns are independent | | Best for | uniform cards, product tiles | photos with varied aspect ratios | | `span` | supported | supported | | Placement order | data order, row by row | shortest column first when `optimizeItemArrangement` is on | ## optimizeItemArrangement With `masonry`, `optimizeItemArrangement` defaults to `true`. The layout manager then puts each single-column item into the **currently shortest column**, keeping column heights even. The trade-off is **order**: item 5 may appear above item 4 because its column was shorter. For a marketplace sorted by price or distance, that can look like a sorting bug. Setting `optimizeItemArrangement={false}` places items sequentially, column after column, preserving reading order at the cost of uneven column bottoms. The source carries a TODO questioning whether the option misbehaves when items resize, so test it with images that load late. ## Choosing for the marketplace - **Uniform product tiles with a few featured listings** - a plain grid with `span: 2` for featured items. Aligned rows read cleanly and keep sort order obvious. - **Photo-led listings with varied aspect ratios** - masonry, deciding explicitly whether even columns or strict order matters more. - **Sponsored banners across the full width** - a span equal to `maxColumns`, plus a separate `getItemType` so banner cells recycle only into banners. ## Spans and recycling A span changes a cell's width, not its recycle pool. A full-width featured listing and a half-width one render the same component, so reusing one for the other is only a props update plus a width change. A full-width sponsored banner is structurally different and should get its own `getItemType` value, so a banner cell is never handed a listing card. ## Common mistakes - Returning `{ span: 2 }` from `overrideItemLayout` instead of mutating `layout`. - Setting `layout.size` from v1 code and expecting v2 to use it. - Reaching for `MasonryFlashList` or `getColumnFlex`, which v2 does not support. - Using masonry for a sorted catalogue and being surprised that the order changes.

  • Why might masonry make a price-sorted marketplace look unsorted?
    Because `optimizeItemArrangement` defaults to `true` with `masonry`, and it places each single-column item into the currently shortest column to keep column heights even. The fifth-cheapest listing can land higher on screen than the fourth. Set `optimizeItemArrangement={false}` for sequential placement, or use a plain grid when order matters more than packing.
  • What happens if you return an object from overrideItemLayout instead of mutating layout?
    Nothing useful: the return value is ignored, so every item keeps the default span of one column. The callback is designed to modify the `layout` object it receives - `layout.span = 2` - and the docs say FlashList falls back to default values when you leave it unchanged.

saying these in an interview costs you the question

  • overrideItemLayout should return a new layout object with span and size.
  • FlashList v2 reads item heights set in overrideItemLayout.
  • Masonry in FlashList v2 still requires the MasonryFlashList component.
  • Masonry always preserves data order from top to bottom in each column.
  • A plain FlashList grid leaves shorter cells at their own height in a row.