Skip to content

Commit 1c034f2

Browse files
committed
Improve ListIterator
1 parent 3694427 commit 1c034f2

File tree

2 files changed

+112
-110
lines changed

2 files changed

+112
-110
lines changed

docs/ListIterator.md

Lines changed: 102 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,52 @@ storybook_path: ra-core-controller-list-listiterator--using-render
88

99
## Usage
1010

11-
Use the `<ListIterator>` component as a child of any component that provides a [`ListContext`](./useListContext.md):
12-
13-
- `<List>`,
14-
- `<ListGuesser>`,
15-
- `<ListBase>`,
16-
- `<ReferenceArrayField>`,
17-
- `<ReferenceManyField>`
11+
Use the `<ListIterator>` component to render a list of records in a custom way. Pass a `render` function to customize how each record is displayed.
1812

1913
{% raw %}
2014
```jsx
2115
import { ListBase, ListIterator } from 'react-admin';
22-
import { OrderedList, ListItem } from 'my-favorite-ui-lib';
2316

24-
const DashboardMostVisitedPosts = () => (
25-
<ListBase resource="posts" sort={{ field: 'views', order: 'DESC' }} page={1} perPage={20}>
26-
<OrderedList>
17+
const MostVisitedPosts = () => (
18+
<ListBase
19+
resource="posts"
20+
sort={{ field: 'views', order: 'DESC' }}
21+
perPage={20}
22+
>
23+
<ul>
2724
<ListIterator
28-
render={record => <ListItem>{record.title} - {record.views}</ListItem>}
25+
render={record => <li>{record.title} - {record.views}</li>}
2926
/>
30-
</OrderedList>
27+
</ul>
3128
</ListBase>
3229
);
3330
```
3431
{% endraw %}
3532

33+
You can use `<ListIterator>` as a child of any component that provides a [`ListContext`](./useListContext.md), such as:
34+
35+
- [`<List>`](./List.md),
36+
- [`<ListGuesser>`](./ListGuesser.md),
37+
- [`<ListBase>`](./ListBase.md),
38+
- [`<ReferenceArrayField>`](./ReferenceArrayField.md),
39+
- [`<ReferenceManyField>`](./ReferenceManyField.md)
40+
41+
**Tip**: React-admin provides several list components that use `<ListIterator>` internally, that you should prefer if you want to render a list of records in a standard way:
42+
43+
- [`<DataTable>`](./DataTable.md) renders a list of records in a table format.
44+
- [`<SimpleList>`](./SimpleList.md) renders a list of records in a simple format, suitable for mobile devices.
45+
- [`<SingleFieldList>`](./SingleFieldList.md) renders a list of records with a single field.
46+
3647
## Props
3748

38-
Here are all the props you can set on the `<AccordionForm>` component:
49+
Here are all the props you can set on the `<ListIterator>` component:
3950

4051
| Prop | Required | Type | Default | Description |
4152
| ----------- | -------- | ---------------------------------- | ------- | ---------------------------------------------------------------------------------------------------- |
4253
| `children` | Optional | `ReactNode` | - | The content to render for each record |
4354
| `data` | Optional | `RaRecord[]` | - | The records. Defaults to the `data` from the `ListContext` |
4455
| `empty` | Optional | `ReactNode` | `null` | The content to display when there is no data |
56+
| `error` | Optional | `ReactNode` | `null` | The content to display when the data fetching fails |
4557
| `isPending` | Optional | `boolean` | - | A boolean indicating whether the data is pending. Defaults to the `isPending` from the `ListContext` |
4658
| `loading` | Optional | `ReactNode` | `null` | The content to display while the data is loading |
4759
| `render` | Optional | `(record: RaRecord) => ReactNode` | - | A function that returns the content to render for each record |
@@ -51,30 +63,24 @@ Additional props are passed to `react-hook-form`'s [`useForm` hook](https://reac
5163

5264
## `children`
5365

54-
If provided, `ListIterator` will render the `children` prop for each record. This is useful when the components you render leverages the [`RecordContext`](./useRecordContext.md):
66+
If provided, `ListIterator` will render the `children` prop once for each record, inside a [`RecordContext`](./useRecordContext.md).
5567

5668
{% raw %}
5769
```tsx
58-
import { ListBase, ListIterator, useRecordContext } from 'react-admin';
59-
import { OrderedList, ListItem } from 'my-favorite-ui-lib';
60-
61-
const DashboardMostVisitedPosts = () => (
62-
<ListBase resource="posts" sort={{ field: 'views', order: 'DESC' }} page={1} perPage={20}>
63-
<OrderedList>
64-
<ListIterator>
65-
<PostItem />
66-
</ListIterator>
67-
</OrderedList>
68-
</ListBase>
70+
import { ListIterator, useRecordContext } from 'react-admin';
71+
72+
const PostList = () => (
73+
<ul>
74+
<ListIterator>
75+
<PostItem />
76+
</ListIterator>
77+
</ul>
6978
);
7079

7180
const PostItem = () => {
7281
const record = useRecordContext();
7382
if (!record) return null;
74-
75-
return (
76-
<ListItem>{record.title} - {record.views}</ListItem>
77-
);
83+
return <li>{record.title} - {record.views}</li>;
7884
};
7985
```
8086
{% endraw %}
@@ -88,20 +94,17 @@ Although `<ListIterator>` reads the data from the closest [`<ListContext>`](./us
8894
{% raw %}
8995
```jsx
9096
import { ListIterator } from 'react-admin';
91-
import { OrderedList, ListItem } from 'my-favorite-ui-lib';
9297
import { customerSegments } from './customerSegments.json';
9398

94-
const MyComponent = () => {
95-
return (
96-
<OrderedList>
97-
<ListIterator
98-
data={customerSegments}
99-
total={customerSegments.length}
100-
render={record => <ListItem>{record.name}</ListItem>}
101-
/>
102-
</OrderedList>
103-
);
104-
}
99+
const PostList = () => (
100+
<ul>
101+
<ListIterator
102+
data={customerSegments}
103+
total={customerSegments.length}
104+
render={record => <li>{record.name}</li>}
105+
/>
106+
</ul>
107+
);
105108
```
106109
{% endraw %}
107110

@@ -112,17 +115,33 @@ To provide a custom UI when there is no data, use the `empty` prop.
112115
{% raw %}
113116
```jsx
114117
import { ListBase, ListIterator } from 'react-admin';
115-
import { OrderedList, ListItem } from 'my-favorite-ui-lib';
116118

117-
const DashboardMostVisitedPosts = () => (
118-
<ListBase resource="posts" sort={{ field: 'views', order: 'DESC' }} page={1} perPage={20}>
119-
<OrderedList>
120-
<ListIterator
121-
empty={<ListItem>No posts found</ListItem>}
122-
render={record => <ListItem>{record.title} - {record.views}</ListItem>}
123-
/>
124-
</OrderedList>
125-
</ListBase>
119+
const PostList = () => (
120+
<ul>
121+
<ListIterator
122+
empty={<li>No posts found</li>}
123+
render={record => <li>{record.title} - {record.views}</li>}
124+
/>
125+
</ul>
126+
);
127+
```
128+
{% endraw %}
129+
130+
## `error`
131+
132+
To provide a custom UI when the data fetching fails, use the `error` prop.
133+
134+
{% raw %}
135+
```jsx
136+
import { ListIterator } from 'react-admin';
137+
138+
const PostList = () => (
139+
<ul>
140+
<ListIterator
141+
error={<li>Error loading posts</li>}
142+
render={record => <li>{record.title} - {record.views}</li>}
143+
/>
144+
</ul>
126145
);
127146
```
128147
{% endraw %}
@@ -133,8 +152,7 @@ Although `<ListIterator>` reads the `isPending` from the closest [`<ListContext>
133152

134153
{% raw %}
135154
```tsx
136-
import { ListBase, ListIterator } from 'react-admin';
137-
import { OrderedList, ListItem } from 'my-favorite-ui-lib';
155+
import { ListIterator } from 'react-admin';
138156
import { useQuery } from '@tanstack/react-query';
139157
import { fetchPostAnalytics } from './fetchPostAnalytics';
140158

@@ -145,13 +163,13 @@ const DashboardMostVisitedPosts = () => {
145163
});
146164

147165
return (
148-
<OrderedList>
166+
<ul>
149167
<ListIterator
150168
data={data}
151169
isPending={isPending}
152-
render={record => <ListItem>{record.title} - {record.views}</ListItem>}
170+
render={record => <li>{record.title} - {record.views}</li>}
153171
/>
154-
</OrderedList>
172+
</ul>
155173
);
156174
}
157175
```
@@ -160,43 +178,38 @@ const DashboardMostVisitedPosts = () => {
160178

161179
## `loading`
162180

163-
To provide a custom UI while the data is loading use the `loading` prop.
181+
To provide a custom UI while the data is loading, use the `loading` prop.
164182

165183
{% raw %}
166184
```jsx
167-
import { ListBase, ListIterator } from 'react-admin';
168-
import { OrderedList, ListItem, Skeleton } from 'my-favorite-ui-lib';
169-
170-
const DashboardMostVisitedPosts = () => (
171-
<ListBase resource="posts" sort={{ field: 'views', order: 'DESC' }} page={1} perPage={20}>
172-
<OrderedList>
173-
<ListIterator
174-
loading={<Skeleton />}
175-
render={record => <ListItem>{record.title} - {record.views}</ListItem>}
176-
/>
177-
</OrderedList>
178-
</ListBase>
185+
import { ListIterator } from 'react-admin';
186+
import { Skeleton } from 'my-favorite-ui-lib';
187+
188+
const PostList = () => (
189+
<ul>
190+
<ListIterator
191+
loading={<Skeleton />}
192+
render={record => <li>{record.title} - {record.views}</li>}
193+
/>
194+
</ul>
179195
);
180196
```
181197
{% endraw %}
182198

183199
## `render`
184200

185-
If provided, `ListIterator` will call the `render` prop for each record. This is useful when the components you render don't leverage the [`RecordContext`](./useRecordContext.md):
201+
If provided, `ListIterator` will call the `render` prop for each record. This is useful when the components you render need the record data to render themselves, or when you want to pass additional props to the rendered component.
186202

187203
{% raw %}
188204
```tsx
189205
import { ListBase, ListIterator } from 'react-admin';
190-
import { OrderedList, ListItem } from 'my-favorite-ui-lib';
191206

192-
const DashboardMostVisitedPosts = () => (
193-
<ListBase resource="posts" sort={{ field: 'views', order: 'DESC' }} page={1} perPage={20}>
194-
<OrderedList>
195-
<ListIterator
196-
render={record => <ListItem>{record.title} - {record.views}</ListItem>}
197-
/>
198-
</OrderedList>
199-
</ListBase>
207+
const PostList = () => (
208+
<ul>
209+
<ListIterator
210+
render={record => <li>{record.title} - {record.views}</li>}
211+
/>
212+
</ul>
200213
);
201214
```
202215
{% endraw %}
@@ -210,20 +223,17 @@ Although `<ListIterator>` reads the total from the closest [`<ListContext>`](./u
210223
{% raw %}
211224
```jsx
212225
import { ListIterator } from 'react-admin';
213-
import { OrderedList, ListItem } from 'my-favorite-ui-lib';
214226
import { customerSegments } from './customerSegments.json';
215227

216-
const MyComponent = () => {
217-
return (
218-
<OrderedList>
219-
<ListIterator
220-
data={customerSegments}
221-
total={customerSegments.length}
222-
render={record => <ListItem>{record.name}</ListItem>}
223-
/>
224-
</OrderedList>
225-
);
226-
}
228+
const PostList = () => (
229+
<ul>
230+
<ListIterator
231+
data={customerSegments}
232+
total={customerSegments.length}
233+
render={record => <li>{record.name}</li>}
234+
/>
235+
</ul>
236+
);
227237
```
228238
{% endraw %}
229239

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

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ import { RecordContextProvider } from '../record';
66
export const ListIterator = <RecordType extends RaRecord = any>(
77
props: ListIteratorProps<RecordType>
88
) => {
9-
const { children, empty = null, loading = null, render } = props;
10-
const { data, total, isPending } =
9+
const { children, empty, error: errorElement, loading, render } = props;
10+
const { data, total, isPending, error } =
1111
useListContextWithProps<RecordType>(props);
1212

1313
if (isPending === true) {
1414
return loading ? loading : null;
1515
}
1616

17+
if (error) {
18+
return errorElement
19+
? React.cloneElement(errorElement, { error })
20+
: null;
21+
}
22+
1723
if (data == null || data.length === 0 || total === 0) {
1824
return empty ? empty : null;
1925
}
@@ -24,29 +30,14 @@ export const ListIterator = <RecordType extends RaRecord = any>(
2430
);
2531
}
2632

27-
if (render) {
28-
return (
29-
<>
30-
{data.map((record, index) => (
31-
<RecordContextProvider
32-
key={record.id ?? `row${index}`}
33-
value={record}
34-
>
35-
{render(record, index)}
36-
</RecordContextProvider>
37-
))}
38-
</>
39-
);
40-
}
41-
4233
return (
4334
<>
4435
{data.map((record, index) => (
4536
<RecordContextProvider
4637
key={record.id ?? `row${index}`}
4738
value={record}
4839
>
49-
{children}
40+
{render ? render(record, index) : children}
5041
</RecordContextProvider>
5142
))}
5243
</>
@@ -57,6 +48,7 @@ export interface ListIteratorProps<RecordType extends RaRecord = any> {
5748
children?: React.ReactNode;
5849
empty?: React.ReactElement;
5950
loading?: React.ReactElement;
51+
error?: React.ReactElement;
6052
render?: (record: RecordType, index: number) => React.ReactNode;
6153
data?: RecordType[];
6254
total?: number;

0 commit comments

Comments
 (0)