Skip to content

Commit 9b35ca7

Browse files
committed
add render prop on InfiniteList
1 parent 3dc9e9e commit 9b35ca7

File tree

4 files changed

+106
-4
lines changed

4 files changed

+106
-4
lines changed

docs/InfiniteList.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ The props are the same as [the `<List>` component](./List.md):
5858

5959
| Prop | Required | Type | Default | Description |
6060
|----------------------------|----------|----------------|-------------------------|----------------------------------------------------------------------------------------------|
61-
| `children` | Required | `ReactNode` | - | The component to use to render the list of records. |
61+
| `children` | Required if no render | `ReactNode` | - | The component to use to render the list of records. |
62+
| `render` | Required if no children | `ReactNode` | - | A function that render the list of records, receives the list context as argument. |
6263
| `actions` | Optional | `ReactElement` | - | The actions to display in the toolbar. |
63-
| `aside` | Optional | `ReactElement` | - | The component to display on the side of the list. |
64+
| `aside` | Optional | `(listContext) => ReactElement` | - | The component to display on the side of the list. |
6465
| `component` | Optional | `Component` | `Card` | The component to render as the root element. |
6566
| `debounce` | Optional | `number` | `500` | The debounce delay in milliseconds to apply when users change the sort or filter parameters. |
6667
| `disable Authentication` | Optional | `boolean` | `false` | Set to `true` to disable the authentication check. |
@@ -84,6 +85,33 @@ Check the [`<List>` component](./List.md) for details about each prop.
8485

8586
Additional props are passed down to the root component (a MUI `<Card>` by default).
8687

88+
## `render`
89+
90+
Alternatively to children you can pass a render prop to `<InfiniteList>`. The render prop will receive the list context as its argument, allowing to inline the render logic for the list content.
91+
When receiving a render prop the `<InfiniteList>` component will ignore the children property.
92+
93+
{% raw %}
94+
```tsx
95+
<InfiniteList
96+
render={({ error, isPending }) => {
97+
if (isPending) {
98+
return <div>Loading...</div>;
99+
}
100+
if (error) {
101+
return <div>Error: {error.message}</div>;
102+
}
103+
return (
104+
<SimpleList
105+
primaryText="%{title} (%{year})"
106+
secondaryText="%{summary}"
107+
tertiaryText={record => record.year}
108+
/>
109+
);
110+
}}
111+
/>
112+
```
113+
{% endraw %}
114+
87115
## `pagination`
88116

89117
You can replace the default "load on scroll" pagination (triggered by a component named `<InfinitePagination>`) by a custom pagination component. To get the pagination state and callbacks, you'll need to read the `InfinitePaginationContext`.
Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
import * as React from 'react';
22
import expect from 'expect';
3-
import { render, screen } from '@testing-library/react';
3+
import { render, screen, waitFor } from '@testing-library/react';
44

5-
import { Themed } from './InfiniteList.stories';
5+
import { Themed, WithRenderProp } from './InfiniteList.stories';
66

77
describe('<InfiniteList />', () => {
8+
let originalIntersectionObserver;
9+
beforeAll(() => {
10+
originalIntersectionObserver = window.IntersectionObserver;
11+
const intersectionObserverMock = () => ({
12+
observe: () => null,
13+
unobserve: () => null,
14+
});
15+
window.IntersectionObserver = jest
16+
.fn()
17+
.mockImplementation(intersectionObserverMock);
18+
});
19+
afterAll(() => {
20+
window.IntersectionObserver = originalIntersectionObserver;
21+
});
22+
823
it('should be customized by a theme', async () => {
924
render(<Themed />);
1025
expect(screen.queryByTestId('themed-list').classList).toContain(
1126
'custom-class'
1227
);
1328
});
29+
it('should render a list page using render prop', async () => {
30+
render(<WithRenderProp />);
31+
expect(screen.getByText('Loading...')).toBeDefined();
32+
33+
await waitFor(() => {
34+
screen.getByText('War and Peace');
35+
screen.getByText('Leo Tolstoy');
36+
});
37+
});
1438
});

packages/ra-ui-materialui/src/list/InfiniteList.stories.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,3 +478,47 @@ export const Themed = () => (
478478
/>
479479
</Admin>
480480
);
481+
482+
export const WithRenderProp = () => (
483+
<Admin
484+
dataProvider={{
485+
...dataProvider,
486+
getList: (resource, params) =>
487+
dataProvider
488+
.getList(resource, params)
489+
.then(({ data, total }) => ({
490+
data,
491+
pageInfo: {
492+
hasNextPage:
493+
total! >
494+
params.pagination.page *
495+
params.pagination.perPage,
496+
hasPreviousPage: params.pagination.page > 1,
497+
},
498+
})),
499+
}}
500+
>
501+
<Resource
502+
name="books"
503+
list={() => (
504+
<InfiniteList
505+
render={({ error, isPending }) => {
506+
if (isPending) {
507+
return <div>Loading...</div>;
508+
}
509+
if (error) {
510+
return <div>Error: {error.message}</div>;
511+
}
512+
return (
513+
<SimpleList
514+
primaryText="%{title}"
515+
secondaryText="%{author}"
516+
tertiaryText={record => record.year}
517+
/>
518+
);
519+
}}
520+
/>
521+
)}
522+
/>
523+
</Admin>
524+
);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ export const InfiniteList = <RecordType extends RaRecord = any>(
8383
name: PREFIX,
8484
});
8585

86+
if (!props.render && !props.children) {
87+
throw new Error(
88+
'<InfiniteList> requires either a `render` prop or `children` prop'
89+
);
90+
}
91+
8692
return (
8793
<InfiniteListBase<RecordType>
8894
debounce={debounce}

0 commit comments

Comments
 (0)