In a FlashList v2 marketplace grid, how do you make a featured listing span two columns, and when do you need masonry instead?
answer
- mutate layout.span, do not return
- span only, no sizes in v2
- grid rows match the tallest cell
- masonry: columns grow independently
- shortest column changes visual order
basics
~20 sIn 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 sA 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 linesimport { 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
Recall that numColumns makes a grid, overrideItemLayout sets span for wide items, and masonry is a prop in FlashList v2.
Explain mutating layout.span, why v2 ignores sizes, how grid rows align to the tallest cell, and how masonry differs.
Choose grid or masonry from content and sort requirements, handle optimizeItemArrangement's order trade-off, and pair full-width spans with item types.
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.