Provide a general summary of the issue here
When a collection is virtualized with <Virtualizer> (e.g. a virtualized Table), the Virtualizer publishes a CollectionRendererContext ({ isVirtualized: true, CollectionRoot, layout, dropTargetDelegate }) that wraps its entire React subtree. Any other collection component (Menu, ListBox, Select, …) rendered inside an overlay (Popover / Dialog) that is a React descendant of the virtualized collection inherits that context — React context propagates through portals — and renders its own collection with the ancestor's CollectionRoot / layout.
If the ancestor layout is a TableLayout (virtualized Table), the nested overlay collection (a Menu/ListBox collection, which has no columns) is laid out with TableLayout, and TableLayout.buildCollection() throws:
TypeError: collection.columns is not iterable
In practice: a virtualized Table whose column headers contain menus/selects/filter popovers crashes the whole table (via the error boundary) the moment any of those popovers is opened.
🤔 Expected Behavior?
Overlay content (Popover / Dialog / Modal) should not inherit a virtualized ancestor's CollectionRendererContext. A Menu / ListBox / Select opened from inside a virtualized Table (or any virtualized collection) should render its own collection with the default renderer (or with its own Virtualizer if one is explicitly nested), not with the ancestor collection's layout. Opening the popover should just work.
😯 Current Behavior
Opening the overlay throws and unmounts the tree:
TypeError: collection.columns is not iterable
at TableLayout.buildCollection (TableLayout.ts:169:35)
at TableLayout.update (ListLayout.ts:359:27) // super.update
at TableLayout.update (TableLayout.ts:162:11)
at Virtualizer.relayout (Virtualizer.ts:194:17)
at Virtualizer.render (Virtualizer.ts:419:12)
at useVirtualizerState (useVirtualizerState.ts:91:34)
at CollectionRoot (Virtualizer.tsx:108:15)
Root cause chain:
Virtualizer (Virtualizer.tsx) provides CollectionRendererContext with { isVirtualized: true, CollectionRoot, layout, dropTargetDelegate } around its children.
Menu (Menu.tsx, let { isVirtualized, CollectionRoot } = useContext(CollectionRendererContext)) and ListBox (ListBox.tsx, reads isVirtualized / CollectionRoot / layoutDelegate from the same context) render their collection through the inherited CollectionRoot. Neither resets the context, and Popover/Dialog do not reset it either.
- Because the popover content is still a React descendant of the
Virtualizer (portals preserve React context), the menu/listbox is laid out with the ancestor's TableLayout.
TableLayout.buildCollection() does for (let column of collection.columns), but the overlay's collection is a Menu/ListBox collection (not a TableCollection), so collection.columns is undefined → not iterable.
Note: a real (even empty) TableCollection always exposes columns (columns: GridNode<T>[] = []), so this only happens because a non-table collection reaches the table layout.
💁 Possible Solution
The overlay boundary should reset the collection renderer so nested collections don't inherit an unrelated virtualized ancestor's renderer. Options:
- Have
Popover / Dialog / Modal (or the overlay Provider inside them) reset CollectionRendererContext to DefaultCollectionRenderer for their children, or
- Have
MenuTrigger / Select / ComboBox reset it around the Popover + ListBox/Menu they render.
Consumer-side workaround (works today, since both symbols are publicly exported from react-aria-components):
import { CollectionRendererContext, DefaultCollectionRenderer } from 'react-aria-components'
<Popover>
<CollectionRendererContext.Provider value={DefaultCollectionRenderer}>
{/* Menu / ListBox / Select content */}
</CollectionRendererContext.Provider>
</Popover>
This is a no-op outside a virtualizer (the context is already DefaultCollectionRenderer), and a nested explicit <Virtualizer> inside the popover still overrides it, so no legitimate case is broken.
🔦 Context
We virtualize a data grid built on Table using Virtualizer + a custom TableLayout subclass. Column header cells contain menus (sort/reorder/resize actions), a column-select menu, and filter popovers. After migrating the grid to Virtualizer, opening any header popover crashes the entire grid through the error boundary — it makes header menus/selects/filters unusable in a virtualized table. This is a trading application where the grid is central, so the whole feature is blocked without the workaround above.
🖥️ Steps to Reproduce
Minimal reproduction (a virtualized Table whose column header contains a MenuTrigger → Popover → Menu):
import {
Virtualizer,
TableLayout,
Table,
TableHeader,
TableBody,
Column,
Row,
Cell,
MenuTrigger,
Button,
Popover,
Menu,
MenuItem,
} from 'react-aria-components'
function Example() {
return (
<div style={{ height: 200 }}>
<Virtualizer layout={TableLayout} layoutOptions={{ rowHeight: 32 }}>
<Table aria-label="Example">
<TableHeader>
<Column isRowHeader>
Name
<MenuTrigger>
<Button aria-label="Menu">☰</Button>
<Popover>
<Menu>
<MenuItem>Sort ascending</MenuItem>
<MenuItem>Sort descending</MenuItem>
</Menu>
</Popover>
</MenuTrigger>
</Column>
</TableHeader>
<TableBody>
<Row>
<Cell>Alpha</Cell>
</Row>
<Row>
<Cell>Bravo</Cell>
</Row>
</TableBody>
</Table>
</Virtualizer>
</div>
)
}
Steps:
- Render the virtualized table above — it renders fine.
- Click the
☰ menu button in the column header to open the Popover + Menu.
- The app throws
TypeError: collection.columns is not iterable and the tree unmounts (error boundary).
The same crash occurs with Select/ComboBox/ListBox inside a Popover in the header, and generally with any Menu/ListBox/Select rendered in an overlay under a virtualized Table.
(A live StackBlitz/CodeSandbox based on the snippet above can be added.)
Version
1.20.0
What browsers are you seeing the problem on?
Chrome
If other, please specify.
No response
What operating system are you using?
MacOS
🧢 Your Company/Team
No response
🕷 Tracking Issue
No response
Provide a general summary of the issue here
When a collection is virtualized with
<Virtualizer>(e.g. a virtualizedTable), theVirtualizerpublishes aCollectionRendererContext({ isVirtualized: true, CollectionRoot, layout, dropTargetDelegate }) that wraps its entire React subtree. Any other collection component (Menu,ListBox,Select, …) rendered inside an overlay (Popover/Dialog) that is a React descendant of the virtualized collection inherits that context — React context propagates through portals — and renders its own collection with the ancestor'sCollectionRoot/layout.If the ancestor layout is a
TableLayout(virtualizedTable), the nested overlay collection (aMenu/ListBoxcollection, which has nocolumns) is laid out withTableLayout, andTableLayout.buildCollection()throws:In practice: a virtualized
Tablewhose column headers contain menus/selects/filter popovers crashes the whole table (via the error boundary) the moment any of those popovers is opened.🤔 Expected Behavior?
Overlay content (
Popover/Dialog/Modal) should not inherit a virtualized ancestor'sCollectionRendererContext. AMenu/ListBox/Selectopened from inside a virtualizedTable(or any virtualized collection) should render its own collection with the default renderer (or with its ownVirtualizerif one is explicitly nested), not with the ancestor collection's layout. Opening the popover should just work.😯 Current Behavior
Opening the overlay throws and unmounts the tree:
Root cause chain:
Virtualizer(Virtualizer.tsx) providesCollectionRendererContextwith{ isVirtualized: true, CollectionRoot, layout, dropTargetDelegate }around its children.Menu(Menu.tsx,let { isVirtualized, CollectionRoot } = useContext(CollectionRendererContext)) andListBox(ListBox.tsx, readsisVirtualized/CollectionRoot/layoutDelegatefrom the same context) render their collection through the inheritedCollectionRoot. Neither resets the context, andPopover/Dialogdo not reset it either.Virtualizer(portals preserve React context), the menu/listbox is laid out with the ancestor'sTableLayout.TableLayout.buildCollection()doesfor (let column of collection.columns), but the overlay's collection is aMenu/ListBoxcollection (not aTableCollection), socollection.columnsisundefined→ not iterable.Note: a real (even empty)
TableCollectionalways exposescolumns(columns: GridNode<T>[] = []), so this only happens because a non-table collection reaches the table layout.💁 Possible Solution
The overlay boundary should reset the collection renderer so nested collections don't inherit an unrelated virtualized ancestor's renderer. Options:
Popover/Dialog/Modal(or the overlayProviderinside them) resetCollectionRendererContexttoDefaultCollectionRendererfor their children, orMenuTrigger/Select/ComboBoxreset it around thePopover+ListBox/Menuthey render.Consumer-side workaround (works today, since both symbols are publicly exported from
react-aria-components):This is a no-op outside a virtualizer (the context is already
DefaultCollectionRenderer), and a nested explicit<Virtualizer>inside the popover still overrides it, so no legitimate case is broken.🔦 Context
We virtualize a data grid built on
TableusingVirtualizer+ a customTableLayoutsubclass. Column header cells contain menus (sort/reorder/resize actions), a column-select menu, and filter popovers. After migrating the grid toVirtualizer, opening any header popover crashes the entire grid through the error boundary — it makes header menus/selects/filters unusable in a virtualized table. This is a trading application where the grid is central, so the whole feature is blocked without the workaround above.🖥️ Steps to Reproduce
Minimal reproduction (a virtualized
Tablewhose column header contains aMenuTrigger→Popover→Menu):Steps:
☰menu button in the column header to open thePopover+Menu.TypeError: collection.columns is not iterableand the tree unmounts (error boundary).The same crash occurs with
Select/ComboBox/ListBoxinside aPopoverin the header, and generally with anyMenu/ListBox/Selectrendered in an overlay under a virtualizedTable.(A live StackBlitz/CodeSandbox based on the snippet above can be added.)
Version
1.20.0
What browsers are you seeing the problem on?
Chrome
If other, please specify.
No response
What operating system are you using?
MacOS
🧢 Your Company/Team
No response
🕷 Tracking Issue
No response