Skip to content

Commit dbc946d

Browse files
committed
Introduce emptyWhileLoading on <ListBase>
1 parent 704ad2c commit dbc946d

File tree

3 files changed

+90
-46
lines changed

3 files changed

+90
-46
lines changed

packages/ra-core/src/controller/list/ListBase.spec.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
33
import {
44
AccessControl,
55
DefaultTitle,
6+
EmptyWhileLoading,
67
NoAuthProvider,
78
Offline,
89
WithAuthProviderNoAccessControl,
910
WithRenderProps,
1011
} from './ListBase.stories';
1112
import { testDataProvider } from '../../dataProvider';
13+
import { onlineManager } from '@tanstack/react-query';
1214

1315
describe('ListBase', () => {
16+
beforeEach(() => {
17+
onlineManager.setOnline(true);
18+
});
1419
it('should load data immediately if authProvider is not provided', async () => {
1520
const dataProvider = testDataProvider({
1621
// @ts-ignore
@@ -144,7 +149,7 @@ describe('ListBase', () => {
144149
it('should render the offline prop node when offline', async () => {
145150
const { rerender } = render(<Offline isOnline={false} />);
146151
await screen.findByText('You are offline, cannot load data');
147-
rerender(<Offline isOnline={true} />);
152+
rerender(<Offline isOnline />);
148153
await screen.findByText('War and Peace');
149154
expect(
150155
screen.queryByText('You are offline, cannot load data')
@@ -153,11 +158,17 @@ describe('ListBase', () => {
153158
await screen.findByText('You are offline, the data may be outdated');
154159
fireEvent.click(screen.getByText('next'));
155160
await screen.findByText('You are offline, cannot load data');
156-
rerender(<Offline isOnline={true} />);
161+
rerender(<Offline isOnline />);
157162
await screen.findByText('And Then There Were None');
158163
rerender(<Offline isOnline={false} />);
159164
fireEvent.click(screen.getByText('previous'));
160165
await screen.findByText('War and Peace');
161166
await screen.findByText('You are offline, the data may be outdated');
162167
});
168+
it('should render nothing while loading if emptyWhileLoading is set to true', async () => {
169+
render(<EmptyWhileLoading />);
170+
expect(screen.queryByText('War and Peace')).toBeNull();
171+
fireEvent.click(screen.getByText('Resolve books loading'));
172+
await screen.findByText('War and Peace');
173+
});
163174
});

packages/ra-core/src/controller/list/ListBase.stories.tsx

Lines changed: 66 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useListContext } from './useListContext';
99
import {
1010
AuthProvider,
1111
DataProvider,
12+
GetListResult,
1213
I18nProvider,
1314
IsOffline,
1415
ListBaseProps,
@@ -51,11 +52,8 @@ const data = {
5152
],
5253
};
5354

54-
const defaultDataProvider = fakeRestProvider(
55-
data,
56-
process.env.NODE_ENV !== 'test',
57-
300
58-
);
55+
const defaultDataProvider = (delay = 300) =>
56+
fakeRestProvider(data, process.env.NODE_ENV !== 'test', delay);
5957

6058
const BookListView = () => {
6159
const {
@@ -128,7 +126,7 @@ const BookListView = () => {
128126
};
129127

130128
export const NoAuthProvider = ({
131-
dataProvider = defaultDataProvider,
129+
dataProvider = defaultDataProvider(),
132130
}: {
133131
dataProvider?: DataProvider;
134132
}) => (
@@ -146,7 +144,7 @@ export const WithAuthProviderNoAccessControl = ({
146144
checkAuth: () => new Promise(resolve => setTimeout(resolve, 300)),
147145
checkError: () => Promise.resolve(),
148146
},
149-
dataProvider = defaultDataProvider,
147+
dataProvider = defaultDataProvider(),
150148
ListProps,
151149
}: {
152150
authProvider?: AuthProvider;
@@ -173,7 +171,7 @@ export const AccessControl = ({
173171
checkError: () => Promise.resolve(),
174172
canAccess: () => new Promise(resolve => setTimeout(resolve, 300, true)),
175173
},
176-
dataProvider = defaultDataProvider,
174+
dataProvider = defaultDataProvider(),
177175
}: {
178176
authProvider?: AuthProvider;
179177
dataProvider?: DataProvider;
@@ -190,7 +188,7 @@ export const AccessControl = ({
190188
);
191189

192190
export const SetParams = () => (
193-
<CoreAdminContext dataProvider={defaultDataProvider}>
191+
<CoreAdminContext dataProvider={defaultDataProvider()}>
194192
<ListBase resource="books" perPage={5}>
195193
<BookListView />
196194
</ListBase>
@@ -207,33 +205,33 @@ const ListMetadataInspector = () => {
207205
);
208206
};
209207

210-
export const WithResponseMetadata = () => (
211-
<CoreAdminContext
212-
dataProvider={{
213-
...defaultDataProvider,
214-
getList: async (resource, params) => {
215-
const result = await defaultDataProvider.getList(
216-
resource,
217-
params
218-
);
219-
return {
220-
...result,
221-
meta: {
222-
facets: [
223-
{ value: 'bar', count: 2 },
224-
{ value: 'baz', count: 1 },
225-
],
226-
},
227-
};
228-
},
229-
}}
230-
>
231-
<ListBase resource="books" perPage={5}>
232-
<BookListView />
233-
<ListMetadataInspector />
234-
</ListBase>
235-
</CoreAdminContext>
236-
);
208+
export const WithResponseMetadata = () => {
209+
const dataProvider = defaultDataProvider();
210+
return (
211+
<CoreAdminContext
212+
dataProvider={{
213+
...dataProvider,
214+
getList: async (resource, params) => {
215+
const result = await dataProvider.getList(resource, params);
216+
return {
217+
...result,
218+
meta: {
219+
facets: [
220+
{ value: 'bar', count: 2 },
221+
{ value: 'baz', count: 1 },
222+
],
223+
},
224+
};
225+
},
226+
}}
227+
>
228+
<ListBase resource="books" perPage={5}>
229+
<BookListView />
230+
<ListMetadataInspector />
231+
</ListBase>
232+
</CoreAdminContext>
233+
);
234+
};
237235

238236
const defaultI18nProvider = polyglotI18nProvider(
239237
locale =>
@@ -283,7 +281,7 @@ export const DefaultTitle = ({
283281
translations?: 'default' | 'resource specific';
284282
}) => (
285283
<CoreAdminContext
286-
dataProvider={defaultDataProvider}
284+
dataProvider={defaultDataProvider()}
287285
i18nProvider={i18nProvider}
288286
>
289287
<ListBase resource="books" perPage={5}>
@@ -293,7 +291,7 @@ export const DefaultTitle = ({
293291
);
294292

295293
export const WithRenderProps = ({
296-
dataProvider = defaultDataProvider,
294+
dataProvider = defaultDataProvider(),
297295
}: {
298296
dataProvider?: DataProvider;
299297
}) => (
@@ -358,7 +356,7 @@ DefaultTitle.argTypes = {
358356
};
359357

360358
export const Offline = ({
361-
dataProvider = defaultDataProvider,
359+
dataProvider = defaultDataProvider(),
362360
isOnline = true,
363361
...props
364362
}: {
@@ -440,6 +438,35 @@ Offline.argTypes = {
440438
},
441439
};
442440

441+
export const EmptyWhileLoading = () => {
442+
let resolveGetList: (() => void) | null = null;
443+
const baseProvider = defaultDataProvider(0);
444+
const dataProvider = {
445+
...baseProvider,
446+
getList: (resource, params) => {
447+
return new Promise<GetListResult>(resolve => {
448+
resolveGetList = () =>
449+
resolve(baseProvider.getList(resource, params));
450+
});
451+
},
452+
};
453+
454+
return (
455+
<CoreAdminContext dataProvider={dataProvider}>
456+
<button
457+
onClick={() => {
458+
resolveGetList && resolveGetList();
459+
}}
460+
>
461+
Resolve books loading
462+
</button>
463+
<ListBase resource="books" perPage={5} emptyWhileLoading>
464+
<BookListView />
465+
</ListBase>
466+
</CoreAdminContext>
467+
);
468+
};
469+
443470
const Title = () => {
444471
const { defaultTitle } = useListContext();
445472
const [locale, setLocale] = useLocaleState();

packages/ra-core/src/controller/list/ListBase.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ import { useIsAuthPending } from '../../auth';
4646
*/
4747
export const ListBase = <RecordType extends RaRecord = any>({
4848
children,
49-
render,
49+
emptyWhileLoading,
5050
loading,
5151
offline,
52+
render,
5253
...props
5354
}: ListBaseProps<RecordType>) => {
5455
const controllerProps = useListController<RecordType>(props);
@@ -78,6 +79,8 @@ export const ListBase = <RecordType extends RaRecord = any>({
7879
offline !== undefined &&
7980
offline !== false;
8081

82+
const showEmpty = isPending && !showOffline && emptyWhileLoading;
83+
8184
return (
8285
// We pass props.resource here as we don't need to create a new ResourceContext if the props is not provided
8386
<OptionalResourceContextProvider value={props.resource}>
@@ -86,9 +89,11 @@ export const ListBase = <RecordType extends RaRecord = any>({
8689
? loading
8790
: showOffline
8891
? offline
89-
: render
90-
? render(controllerProps)
91-
: children}
92+
: showEmpty
93+
? null
94+
: render
95+
? render(controllerProps)
96+
: children}
9297
</ListContextProvider>
9398
</OptionalResourceContextProvider>
9499
);
@@ -97,7 +102,8 @@ export const ListBase = <RecordType extends RaRecord = any>({
97102
export interface ListBaseProps<RecordType extends RaRecord = any>
98103
extends ListControllerProps<RecordType> {
99104
children?: ReactNode;
100-
render?: (props: ListControllerResult<RecordType, Error>) => ReactNode;
105+
emptyWhileLoading?: boolean;
101106
loading?: ReactNode;
102107
offline?: ReactNode;
108+
render?: (props: ListControllerResult<RecordType, Error>) => ReactNode;
103109
}

0 commit comments

Comments
 (0)