Skip to content

Commit d72afc3

Browse files
committed
document and test render prop on ReferenceManyField
1 parent ddf9526 commit d72afc3

File tree

3 files changed

+109
-1
lines changed

3 files changed

+109
-1
lines changed

docs/ReferenceManyField.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ This example leverages [`<SingleFieldList>`](./SingleFieldList.md) to display an
8989

9090
| Prop | Required | Type | Default | Description |
9191
| -------------- | -------- | --------------------------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------- |
92-
| `children` | Required | `Element` | - | One or several elements that render a list of records based on a `ListContext` |
92+
| `children` | Required if no render | `Element` | - | One or several elements that render a list of records based on a `ListContext` |
93+
| `render` | Required if no children | `(listContext) => Element` | - | Function that receives a `ListContext` and render elements |
9394
| `debounce` | Optional | `number` | 500 | debounce time in ms for the `setFilters` callbacks |
9495
| `empty` | Optional | `ReactNode` | - | Element to display when there are no related records. |
9596
| `filter` | Optional | `Object` | - | Filters to use when fetching the related records, passed to `getManyReference()` |
@@ -163,6 +164,71 @@ export const AuthorShow = () => (
163164
);
164165
```
165166

167+
## `render`
168+
169+
Alternatively to children you can pass a render prop to `<ReferenceManyField>`. The render prop will receive the list context as its argument, allowing to inline the render logic for both the list and the pagination.
170+
When receiving a render prop the `<ReferenceManyField>` component will ignore the children and the pagination property.
171+
172+
```jsx
173+
import { Show, SimpleShowLayout, ReferenceManyField, DataTable, TextField, DateField } from 'react-admin';
174+
175+
const CustomAuthorView = ({
176+
source,
177+
children,
178+
}: {
179+
source: string;
180+
}) => {
181+
const context = useListController();
182+
183+
if (context.isPending) {
184+
return <p>Loading...</p>;
185+
}
186+
187+
if (context.error) {
188+
return <p className="error">{context.error.toString()}</p>;
189+
}
190+
return (
191+
<p>
192+
{listContext.data?.map((datum, index) => (
193+
<li key={index}>{datum[source]}</li>
194+
))}
195+
</p>
196+
);
197+
};
198+
199+
const AuthorShow = () => (
200+
<Show>
201+
<SimpleShowLayout>
202+
<TextField source="first_name" />
203+
<TextField source="last_name" />
204+
<ReferenceManyField
205+
reference="books"
206+
target="author_id"
207+
render={
208+
(context) => {
209+
210+
if (context.isPending) {
211+
return <p>Loading...</p>;
212+
}
213+
214+
if (context.error) {
215+
return <p className="error">{context.error.toString()}</p>;
216+
}
217+
return (
218+
<p>
219+
{listContext.data?.map((author, index) => (
220+
<li key={index}>{author.name}</li>
221+
))}
222+
</p>
223+
);
224+
}
225+
}
226+
/>
227+
</SimpleShowLayout>
228+
</Show>
229+
);
230+
```
231+
166232
## `debounce`
167233
168234
By default, `<ReferenceManyField>` does not refresh the data as soon as the user enters data in the filter form. Instead, it waits for half a second of user inactivity (via `lodash.debounce`) before calling the `dataProvider` on filter change. This is to prevent repeated (and useless) calls to the API.

packages/ra-ui-materialui/src/field/ReferenceManyField.spec.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
Empty,
1515
WithPagination,
1616
WithPaginationAndSelectAllLimit,
17+
WithRenderProp,
1718
} from './ReferenceManyField.stories';
1819

1920
const theme = createTheme();
@@ -206,6 +207,20 @@ describe('<ReferenceManyField />', () => {
206207
});
207208
});
208209

210+
it('should use render prop when provides', async () => {
211+
render(<WithRenderProp />);
212+
await waitFor(() => {
213+
expect(screen.queryAllByRole('progressbar')).toHaveLength(0);
214+
});
215+
const items = await screen.findAllByRole('listitem');
216+
expect(items).toHaveLength(5);
217+
expect(items[0].textContent).toEqual('War and Peace');
218+
expect(items[1].textContent).toEqual('Anna Karenina');
219+
expect(items[2].textContent).toEqual('Resurrection');
220+
expect(items[3].textContent).toEqual('The Idiot');
221+
expect(items[4].textContent).toEqual('The Last Day of a Condemned');
222+
});
223+
209224
describe('pagination', () => {
210225
it('should render pagination based on total from getManyReference', async () => {
211226
const data = [

packages/ra-ui-materialui/src/field/ReferenceManyField.stories.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,30 @@ export const FullApp = () => (
286286
</Admin>
287287
</TestMemoryRouter>
288288
);
289+
290+
export const WithRenderProp = () => (
291+
<Wrapper>
292+
<ReferenceManyField
293+
reference="books"
294+
target="author_id"
295+
render={({ error, isPending, data }) => {
296+
if (isPending) {
297+
return <p>Loading...</p>;
298+
}
299+
300+
if (error) {
301+
return <p style={{ color: 'red' }}>{error.message}</p>;
302+
}
303+
return (
304+
<>
305+
{data?.map((datum, index) => (
306+
<li role="listitem" key={index}>
307+
{datum.title}
308+
</li>
309+
))}
310+
</>
311+
);
312+
}}
313+
/>
314+
</Wrapper>
315+
);

0 commit comments

Comments
 (0)