Skip to content

Commit 06bb447

Browse files
committed
add render props to ListBase component
1 parent 5cd8c5a commit 06bb447

File tree

6 files changed

+128
-5
lines changed

6 files changed

+128
-5
lines changed

docs/ListBase.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ The `<ListBase>` component accepts the same props as [`useListController`](./use
6969
These are a subset of the props accepted by `<List>` - only the props that change data fetching, and not the props related to the user interface.
7070

7171
In addition, `<ListBase>` renders its children components inside a `ListContext`. Check [the `<List children>` documentation](./List.md#children) for usage examples.
72+
Alternatively you can pass a render function to the props, in place of children. This function will receive the listContext as argument. Check [the `<List render>` documentation](./List.md#render) for usage examples.
7273

7374
## Security
7475

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,45 @@ DefaultTitle.argTypes = {
246246
},
247247
};
248248

249+
export const WithRenderProps = () => (
250+
<CoreAdminContext dataProvider={defaultDataProvider}>
251+
<InfiniteListBase
252+
resource="books"
253+
perPage={5}
254+
render={context => {
255+
const {
256+
hasNextPage,
257+
fetchNextPage,
258+
isFetchingNextPage,
259+
hasPreviousPage,
260+
fetchPreviousPage,
261+
isFetchingPreviousPage,
262+
} = context;
263+
return (
264+
<div>
265+
{hasPreviousPage && (
266+
<button
267+
onClick={() => fetchPreviousPage()}
268+
disabled={isFetchingPreviousPage}
269+
>
270+
Previous
271+
</button>
272+
)}
273+
{hasNextPage && (
274+
<button
275+
onClick={() => fetchNextPage()}
276+
disabled={isFetchingNextPage}
277+
>
278+
Next
279+
</button>
280+
)}
281+
</div>
282+
);
283+
}}
284+
/>
285+
</CoreAdminContext>
286+
);
287+
249288
const Title = () => {
250289
const { defaultTitle } = useListContext();
251290
const [locale, setLocale] = useLocaleState();

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ReactNode } from 'react';
33
import {
44
useInfiniteListController,
55
InfiniteListControllerProps,
6+
InfiniteListControllerResult,
67
} from './useInfiniteListController';
78
import { OptionalResourceContextProvider } from '../../core';
89
import { RaRecord } from '../../types';
@@ -46,6 +47,7 @@ import { useIsAuthPending } from '../../auth';
4647
*/
4748
export const InfiniteListBase = <RecordType extends RaRecord = any>({
4849
children,
50+
render,
4951
loading = null,
5052
...props
5153
}: InfiniteListBaseProps<RecordType>) => {
@@ -59,6 +61,12 @@ export const InfiniteListBase = <RecordType extends RaRecord = any>({
5961
return loading;
6062
}
6163

64+
if (!render && !children) {
65+
throw new Error(
66+
"<InfiniteListBase> requires either a 'render' prop or 'children' prop"
67+
);
68+
}
69+
6270
return (
6371
// We pass props.resource here as we don't need to create a new ResourceContext if the props is not provided
6472
<OptionalResourceContextProvider value={props.resource}>
@@ -83,6 +91,7 @@ export const InfiniteListBase = <RecordType extends RaRecord = any>({
8391

8492
export interface InfiniteListBaseProps<RecordType extends RaRecord = any>
8593
extends InfiniteListControllerProps<RecordType> {
86-
children: ReactNode;
8794
loading?: ReactNode;
95+
children?: ReactNode;
96+
render?: (props: InfiniteListControllerResult<RecordType>) => ReactNode;
8897
}

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,69 @@ export const DefaultTitle = ({
282282
</CoreAdminContext>
283283
);
284284

285+
export const WithRenderProps = ({
286+
dataProvider = defaultDataProvider,
287+
}: {
288+
dataProvider?: DataProvider;
289+
}) => (
290+
<CoreAdminContext dataProvider={dataProvider}>
291+
<ListBase
292+
resource="books"
293+
perPage={5}
294+
render={controllerProps => {
295+
const {
296+
data,
297+
error,
298+
isPending,
299+
sort,
300+
filterValues,
301+
page,
302+
perPage,
303+
setPage,
304+
total,
305+
} = controllerProps;
306+
const defaultValue = JSON.stringify({
307+
page,
308+
perPage,
309+
sort,
310+
filterValues,
311+
});
312+
if (isPending) {
313+
return <div>Loading...</div>;
314+
}
315+
if (error) {
316+
return <div>Error...</div>;
317+
}
318+
319+
return (
320+
<div>
321+
<button
322+
disabled={page <= 1}
323+
onClick={() => setPage(page - 1)}
324+
>
325+
previous
326+
</button>
327+
<span>
328+
Page {page} of {Math.ceil(total / perPage)}
329+
</span>
330+
<button
331+
disabled={page >= total / perPage}
332+
onClick={() => setPage(page + 1)}
333+
>
334+
next
335+
</button>
336+
<ul>
337+
{data.map((record: any) => (
338+
<li key={record.id}>{record.title}</li>
339+
))}
340+
</ul>
341+
</div>
342+
);
343+
}}
344+
></ListBase>
345+
</CoreAdminContext>
346+
);
347+
285348
DefaultTitle.args = {
286349
translations: 'default',
287350
};

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import * as React from 'react';
22
import { ReactNode } from 'react';
3-
import { useListController, ListControllerProps } from './useListController';
3+
import {
4+
useListController,
5+
ListControllerProps,
6+
ListControllerResult,
7+
} from './useListController';
48
import { OptionalResourceContextProvider } from '../../core';
59
import { RaRecord } from '../../types';
610
import { ListContextProvider } from './ListContextProvider';
@@ -42,6 +46,7 @@ import { useIsAuthPending } from '../../auth';
4246
*/
4347
export const ListBase = <RecordType extends RaRecord = any>({
4448
children,
49+
render,
4550
loading = null,
4651
...props
4752
}: ListBaseProps<RecordType>) => {
@@ -54,19 +59,25 @@ export const ListBase = <RecordType extends RaRecord = any>({
5459
if (isAuthPending && !props.disableAuthentication) {
5560
return loading;
5661
}
62+
if (!render && !children) {
63+
throw new Error(
64+
"<ListBase> requires either a 'render' prop or 'children' prop"
65+
);
66+
}
5767

5868
return (
5969
// We pass props.resource here as we don't need to create a new ResourceContext if the props is not provided
6070
<OptionalResourceContextProvider value={props.resource}>
6171
<ListContextProvider value={controllerProps}>
62-
{children}
72+
{render ? render(controllerProps) : children}
6373
</ListContextProvider>
6474
</OptionalResourceContextProvider>
6575
);
6676
};
6777

6878
export interface ListBaseProps<RecordType extends RaRecord = any>
6979
extends ListControllerProps<RecordType> {
70-
children: ReactNode;
80+
children?: ReactNode;
81+
render?: (props: ListControllerResult<RecordType, Error>) => ReactNode;
7182
loading?: ReactNode;
7283
}

packages/ra-ui-materialui/src/list/ListView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export interface ListViewProps {
191191
* </List>
192192
* );
193193
*/
194-
children: ReactNode;
194+
children?: ReactNode;
195195

196196
/**
197197
* The component used to display the list. Defaults to <Card>.

0 commit comments

Comments
 (0)