From b4867c1d661198df45e9fc36a34f41283c164794 Mon Sep 17 00:00:00 2001 From: DPandyan Date: Thu, 29 Jan 2026 15:42:04 -0800 Subject: [PATCH 1/4] nested collections --- .../s2/stories/TableView.stories.tsx | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/@react-spectrum/s2/stories/TableView.stories.tsx b/packages/@react-spectrum/s2/stories/TableView.stories.tsx index e32ff026983..cfaad7d5121 100644 --- a/packages/@react-spectrum/s2/stories/TableView.stories.tsx +++ b/packages/@react-spectrum/s2/stories/TableView.stories.tsx @@ -27,6 +27,9 @@ import { PickerItem, Row, StatusLight, + Tabs, + TabList, + Tab, TableBody, TableHeader, TableView, @@ -41,7 +44,7 @@ import FolderOpen from '../spectrum-illustrations/linear/FolderOpen'; import {Key} from '@react-types/shared'; import type {Meta, StoryObj} from '@storybook/react'; import React, {ReactElement, useCallback, useEffect, useRef, useState} from 'react'; -import {SortDescriptor} from 'react-aria-components'; +import {CollectionRendererContext, DefaultCollectionRenderer, SortDescriptor} from 'react-aria-components'; import {style} from '../style/spectrum-theme' with {type: 'macro'}; import {useAsyncList, useListData} from '@react-stately/data'; import {useEffectEvent} from '@react-aria/utils'; @@ -1714,3 +1717,69 @@ export const EditableTableWithAsyncSaving: StoryObj = { ); } }; + +export const TableViewCollectionError: StoryObj = { + render: function DynamicColumnsExample(args) { + const columns: Array<{ + key: string; + label: string; + align?: 'start' | 'center' | 'end'; + }> = [ + { key: 'name', label: 'Name'}, + { key: 'count', label: 'Count', align: 'end' }, + ]; + + interface DemoItem { + id: string; + name: string; + count: number; + } + + const tabs = [ + { id: 'general', label: 'General' }, + { id: 'advanced', label: 'Advanced' }, + { id: 'about', label: 'About' }, + ]; + const renderEmptyState = () => ( + + {/* */} + + + {tabs.map((tab) => ( + + {tab.label} + + ))} + + + + ); + + return ( + + + + {columns.map((column) => ( + + {column.label} + + ))} + + + items={[]} + renderEmptyState={renderEmptyState}> + + + + ); + }, + args: Example.args, + parameters: { + docs: { + disable: true + } + } +} From bd8c02a16c56b479afe77111a36e26dbc359c79a Mon Sep 17 00:00:00 2001 From: DPandyan Date: Thu, 29 Jan 2026 20:40:43 -0800 Subject: [PATCH 2/4] added test --- .../s2/test/TableView.test.tsx | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/@react-spectrum/s2/test/TableView.test.tsx b/packages/@react-spectrum/s2/test/TableView.test.tsx index 5b49c9d40d4..b922c400459 100644 --- a/packages/@react-spectrum/s2/test/TableView.test.tsx +++ b/packages/@react-spectrum/s2/test/TableView.test.tsx @@ -24,6 +24,7 @@ import { TableView, Text } from '../src'; +import {CollectionRendererContext, DefaultCollectionRenderer, Tab, TabList, Tabs} from 'react-aria-components'; import Filter from '../s2wf-icons/S2_Icon_Filter_20_N.svg'; import React from 'react'; import {User} from '@react-aria/test-utils'; @@ -108,4 +109,44 @@ describe('TableView', () => { await tableTester.triggerColumnHeaderAction({column: 1, action: 0, interactionType: 'keyboard'}); expect(onAction).toHaveBeenCalledTimes(1); }); + + it('should render empty state + nested collection without crashing', async () => { + const tabs = [ + {id: 'general', label: 'General'}, + {id: 'advanced', label: 'Advanced'} + ]; + const renderEmptyState = () => ( + + + + {tabs.map((tab) => ( + + {tab.label} + + ))} + + + + ); + + let renderResult: ReturnType; + await act(async () => { + renderResult = render( + + + {(column) => ( + + {column.name} + + )} + + + + ); + await Promise.resolve(); + }); + let {getAllByRole} = renderResult!; + + expect(getAllByRole('tab')).toHaveLength(tabs.length); + }); }); From 73f34d1d04d1cb98a5a75674aa83f6f1b5324eda Mon Sep 17 00:00:00 2001 From: DPandyan Date: Thu, 5 Feb 2026 13:42:01 -0800 Subject: [PATCH 3/4] lint fix --- .../s2/stories/TableView.stories.tsx | 44 +++++++------------ .../s2/test/TableView.test.tsx | 24 +++++----- 2 files changed, 28 insertions(+), 40 deletions(-) diff --git a/packages/@react-spectrum/s2/stories/TableView.stories.tsx b/packages/@react-spectrum/s2/stories/TableView.stories.tsx index cfaad7d5121..688bd635bbd 100644 --- a/packages/@react-spectrum/s2/stories/TableView.stories.tsx +++ b/packages/@react-spectrum/s2/stories/TableView.stories.tsx @@ -27,24 +27,24 @@ import { PickerItem, Row, StatusLight, - Tabs, - TabList, Tab, TableBody, TableHeader, TableView, TableViewProps, + TabList, + Tabs, Text, TextField } from '../src'; import {categorizeArgTypes, getActionArgs} from './utils'; +import {CollectionRendererContext, DefaultCollectionRenderer, SortDescriptor} from 'react-aria-components'; import Edit from '../s2wf-icons/S2_Icon_Edit_20_N.svg'; import Filter from '../s2wf-icons/S2_Icon_Filter_20_N.svg'; import FolderOpen from '../spectrum-illustrations/linear/FolderOpen'; import {Key} from '@react-types/shared'; import type {Meta, StoryObj} from '@storybook/react'; import React, {ReactElement, useCallback, useEffect, useRef, useState} from 'react'; -import {CollectionRendererContext, DefaultCollectionRenderer, SortDescriptor} from 'react-aria-components'; import {style} from '../style/spectrum-theme' with {type: 'macro'}; import {useAsyncList, useListData} from '@react-stately/data'; import {useEffectEvent} from '@react-aria/utils'; @@ -1719,26 +1719,20 @@ export const EditableTableWithAsyncSaving: StoryObj = { }; export const TableViewCollectionError: StoryObj = { - render: function DynamicColumnsExample(args) { + render: function DynamicColumnsExample() { const columns: Array<{ - key: string; - label: string; - align?: 'start' | 'center' | 'end'; + key: string, + label: string, + align?: 'start' | 'center' | 'end' }> = [ - { key: 'name', label: 'Name'}, - { key: 'count', label: 'Count', align: 'end' }, + {key: 'name', label: 'Name'}, + {key: 'count', label: 'Count', align: 'end'} ]; - interface DemoItem { - id: string; - name: string; - count: number; - } - const tabs = [ - { id: 'general', label: 'General' }, - { id: 'advanced', label: 'Advanced' }, - { id: 'about', label: 'About' }, + {id: 'general', label: 'General'}, + {id: 'advanced', label: 'Advanced'}, + {id: 'about', label: 'About'} ]; const renderEmptyState = () => ( @@ -1757,21 +1751,15 @@ export const TableViewCollectionError: StoryObj = { return ( - + {columns.map((column) => ( - + {column.label} ))} - - items={[]} - renderEmptyState={renderEmptyState}> - + ); @@ -1782,4 +1770,4 @@ export const TableViewCollectionError: StoryObj = { disable: true } } -} +}; diff --git a/packages/@react-spectrum/s2/test/TableView.test.tsx b/packages/@react-spectrum/s2/test/TableView.test.tsx index 7ab2b2bd921..87a2b44cd20 100644 --- a/packages/@react-spectrum/s2/test/TableView.test.tsx +++ b/packages/@react-spectrum/s2/test/TableView.test.tsx @@ -159,11 +159,11 @@ describe('TableView', () => { - {tabs.map((tab) => ( - - {tab.label} - - ))} + {tabs.map((tab) => ( + + {tab.label} + + ))} @@ -173,13 +173,13 @@ describe('TableView', () => { await act(async () => { renderResult = render( - - {(column) => ( - - {column.name} - - )} - + + {(column) => ( + + {column.name} + + )} + ); From 17b7b3bbe0c1c8cbf9abc1d29ea44e36ca7dc9d3 Mon Sep 17 00:00:00 2001 From: DPandyan Date: Fri, 6 Feb 2026 15:52:17 -0800 Subject: [PATCH 4/4] fix: wrap renderEmptyState in CollectionRendererContext.Provider in TableView component --- packages/@react-spectrum/s2/src/TableView.tsx | 6 +++++- .../@react-spectrum/s2/test/TableView.test.tsx | 18 ++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/@react-spectrum/s2/src/TableView.tsx b/packages/@react-spectrum/s2/src/TableView.tsx index b5e66fd84a2..f9b7772a5aa 100644 --- a/packages/@react-spectrum/s2/src/TableView.tsx +++ b/packages/@react-spectrum/s2/src/TableView.tsx @@ -17,10 +17,12 @@ import { ButtonContext, CellRenderProps, Collection, + CollectionRendererContext, ColumnRenderProps, ColumnResizer, ContextValue, DEFAULT_SLOT, + DefaultCollectionRenderer, Form, Key, OverlayTriggerStateContext, @@ -439,7 +441,9 @@ export const TableBody = /*#__PURE__*/ (forwardRef as forwardRefType)(function T if (renderEmptyState != null && !isLoading) { emptyRender = (props: TableBodyRenderProps) => (
- {renderEmptyState(props)} + + {renderEmptyState(props)} +
); } else if (loadingState === 'loading') { diff --git a/packages/@react-spectrum/s2/test/TableView.test.tsx b/packages/@react-spectrum/s2/test/TableView.test.tsx index 87a2b44cd20..9b37f5c1d32 100644 --- a/packages/@react-spectrum/s2/test/TableView.test.tsx +++ b/packages/@react-spectrum/s2/test/TableView.test.tsx @@ -24,11 +24,11 @@ import { TableView, Text } from '../src'; -import {CollectionRendererContext, DefaultCollectionRenderer, Tab, TabList, Tabs} from 'react-aria-components'; import {DisabledBehavior} from '@react-types/shared'; import Filter from '../s2wf-icons/S2_Icon_Filter_20_N.svg'; import {pointerMap, User} from '@react-aria/test-utils'; import React, {useState} from 'react'; +import {Tab, TabList, Tabs} from 'react-aria-components'; import userEvent from '@testing-library/user-event'; // @ts-ignore @@ -157,15 +157,13 @@ describe('TableView', () => { ]; const renderEmptyState = () => ( - - - {tabs.map((tab) => ( - - {tab.label} - - ))} - - + + {tabs.map((tab) => ( + + {tab.label} + + ))} + );