Skip to content

Commit c97efdd

Browse files
jluyauLFDanLu
andauthored
Enable propagation of all value through useAsyncList methods (#2200)
* Enable propagation of all value through useAsyncList methods * review fix * making table story use list.selectedKeys Co-authored-by: Daniel Lu <[email protected]>
1 parent 1c4a540 commit c97efdd

File tree

3 files changed

+71
-4
lines changed

3 files changed

+71
-4
lines changed

packages/@react-spectrum/table/stories/Table.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ function AsyncLoadingExample() {
10081008
return (
10091009
<div>
10101010
<ActionButton marginBottom={10} onPress={() => list.remove(list.items[0].data.id)}>Remove first item</ActionButton>
1011-
<TableView aria-label="Top news from Reddit" selectionMode="multiple" width={1000} height={400} isQuiet sortDescriptor={list.sortDescriptor} onSortChange={list.sort}>
1011+
<TableView aria-label="Top news from Reddit" selectionMode="multiple" width={1000} height={400} isQuiet sortDescriptor={list.sortDescriptor} onSortChange={list.sort} selectedKeys={list.selectedKeys} onSelectionChange={list.setSelectedKeys}>
10121012
<TableHeader>
10131013
<Column key="score" width={100} allowsSorting>Score</Column>
10141014
<Column key="title" isRowHeader allowsSorting>Title</Column>

packages/@react-stately/data/src/useAsyncList.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ interface AsyncListData<T> extends ListData<T> {
111111
}
112112

113113
function reducer<T, C>(data: AsyncListState<T, C>, action: Action<T, C>): AsyncListState<T, C> {
114+
let selectedKeys;
114115
switch (data.state) {
115116
case 'idle':
116117
case 'error':
@@ -151,12 +152,13 @@ function reducer<T, C>(data: AsyncListState<T, C>, action: Action<T, C>): AsyncL
151152
return data;
152153
}
153154

155+
selectedKeys = action.selectedKeys ?? data.selectedKeys;
154156
return {
155157
...data,
156158
filterText: action.filterText ?? data.filterText,
157159
state: 'idle',
158160
items: [...action.items],
159-
selectedKeys: new Set(action.selectedKeys ?? data.selectedKeys),
161+
selectedKeys: selectedKeys === 'all' ? 'all' : new Set(selectedKeys),
160162
sortDescriptor: action.sortDescriptor ?? data.sortDescriptor,
161163
abortController: null,
162164
cursor: action.cursor
@@ -200,12 +202,15 @@ function reducer<T, C>(data: AsyncListState<T, C>, action: Action<T, C>): AsyncL
200202
case 'loadingMore':
201203
switch (action.type) {
202204
case 'success':
205+
selectedKeys = (data.selectedKeys === 'all' || action.selectedKeys === 'all')
206+
? 'all'
207+
: new Set([...data.selectedKeys, ...(action.selectedKeys ?? [])]);
203208
// Append the new items
204209
return {
205210
...data,
206211
state: 'idle',
207212
items: [...data.items, ...action.items],
208-
selectedKeys: new Set([...data.selectedKeys, ...(action.selectedKeys ?? [])]),
213+
selectedKeys,
209214
sortDescriptor: action.sortDescriptor ?? data.sortDescriptor,
210215
abortController: null,
211216
cursor: action.cursor
@@ -274,7 +279,7 @@ export function useAsyncList<T, C = string>(options: AsyncListOptions<T, C>): As
274279
state: 'idle',
275280
error: null,
276281
items: [],
277-
selectedKeys: new Set(initialSelectedKeys),
282+
selectedKeys: initialSelectedKeys === 'all' ? 'all' : new Set(initialSelectedKeys),
278283
sortDescriptor: initialSortDescriptor,
279284
filterText: initialFilterText
280285
});

packages/@react-stately/data/test/useAsyncList.test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,45 @@ describe('useAsyncList', () => {
831831
expect(result.current.items).toEqual(ITEMS2);
832832
});
833833

834+
it('should maintain all selection through a loadMore call', async () => {
835+
let load = jest.fn()
836+
.mockImplementationOnce(getItems)
837+
.mockImplementationOnce(getItems2);
838+
let {result} = renderHook(
839+
() => useAsyncList({load})
840+
);
841+
842+
expect(load).toHaveBeenCalledTimes(1);
843+
expect(result.current.isLoading).toBe(true);
844+
expect(result.current.items).toEqual([]);
845+
846+
await act(async () => {
847+
jest.runAllTimers();
848+
result.current.setSelectedKeys('all');
849+
});
850+
851+
expect(result.current.selectedKeys).toEqual('all');
852+
853+
await act(async () => {
854+
result.current.loadMore();
855+
jest.runAllTimers();
856+
});
857+
858+
expect(load).toHaveBeenCalledTimes(2);
859+
expect(result.current.isLoading).toBe(false);
860+
expect(result.current.items).toEqual([...ITEMS, ...ITEMS2]);
861+
expect(result.current.selectedKeys).toEqual('all');
862+
});
863+
864+
it('should accept all for initialSelectedKeys', () => {
865+
let load = jest.fn()
866+
.mockImplementationOnce(getItems);
867+
let {result} = renderHook(
868+
() => useAsyncList({load, initialSelectedKeys: 'all'})
869+
);
870+
expect(result.current.selectedKeys).toEqual('all');
871+
});
872+
834873
describe('filtering', function () {
835874
const filterItems = [{id: 1, name: 'Bob'}, {id: 2, name: 'Joe'}, {id: 3, name: 'Bob Joe'}];
836875
const itemsFirstCall = [{id: 1, name: 'Bob'}, {id: 3, name: 'Bob Joe'}];
@@ -889,6 +928,29 @@ describe('useAsyncList', () => {
889928
expect(result.current.filterText).toEqual('Blah');
890929
});
891930

931+
it('should preserve all selectedKeys through filtering', async () => {
932+
let load = jest.fn().mockImplementation(getFilterItems);
933+
let initialFilterText = 'Blah';
934+
let {result, waitForNextUpdate} = renderHook(() => useAsyncList({load, initialFilterText}));
935+
936+
await act(async () => {
937+
result.current.setSelectedKeys('all');
938+
});
939+
940+
expect(result.current.selectedKeys).toEqual('all');
941+
942+
await act(async () => {
943+
jest.runAllTimers();
944+
await waitForNextUpdate();
945+
});
946+
947+
expect(result.current.loadingState).toBe('idle');
948+
expect(result.current.isLoading).toBe(false);
949+
expect(result.current.items).toEqual(filterItems);
950+
expect(result.current.filterText).toEqual('Blah');
951+
expect(result.current.selectedKeys).toEqual('all');
952+
});
953+
892954
it('should update the list of items when the filter text changes (server side filtering)', async () => {
893955
let load = jest
894956
.fn()

0 commit comments

Comments
 (0)