Skip to content

Commit 70b260c

Browse files
committed
add doc on ReferenceArrayFieldBase
1 parent b39185d commit 70b260c

File tree

3 files changed

+263
-9
lines changed

3 files changed

+263
-9
lines changed

docs/ReferenceArrayFieldBase.md

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
---
2+
layout: default
3+
title: "The ReferenceArrayFieldBase Component"
4+
storybook_path: ra-ui-materialui-fields-referencearrayfieldbase--basic
5+
---
6+
7+
# `<ReferenceArrayFieldBase>`
8+
9+
Use `<ReferenceArrayFieldBase>` to display a list of related records, via a one-to-many relationship materialized by an array of foreign keys.
10+
11+
`<ReferenceArrayFieldBase>` fetches a list of referenced records (using the `dataProvider.getMany()` method), and puts them in a [`ListContext`](./useListContext.md). This component is headless, and its children need to use the data from this context to render the desired ui.
12+
For a component handling the UI too use [the `<ReferenceArrayField>` component](./ReferenceArrayField.md) instead.
13+
14+
**Tip**: If the relationship is materialized by a foreign key on the referenced resource, use [the `<ReferenceManyFieldBase>` component](./ReferenceManyFieldBase.md) instead.
15+
16+
## Usage
17+
18+
For instance, let's consider a model where a `post` has many `tags`, materialized to a `tags_ids` field containing an array of ids:
19+
20+
```
21+
┌──────────────┐ ┌────────┐
22+
│ posts │ │ tags │
23+
│--------------│ │--------│
24+
│ id │ ┌───│ id │
25+
│ title │ │ │ name │
26+
│ body │ │ └────────┘
27+
│ is_published │ │
28+
│ tag_ids │╾──┘
29+
└──────────────┘
30+
```
31+
32+
A typical `post` record therefore looks like this:
33+
34+
```json
35+
{
36+
"id": 1,
37+
"title": "Hello world",
38+
"body": "...",
39+
"is_published": true,
40+
"tags_ids": [1, 2, 3]
41+
}
42+
```
43+
44+
In that case, use `<ReferenceArrayFieldBase>` to display the post tag names as Chips as follows:
45+
46+
```jsx
47+
import { List, DataTable, ReferenceArrayFieldBase } from 'react-admin';
48+
49+
const MyPostView = (props: { children: React.ReactNode }) => {
50+
const context = useListContext();
51+
52+
if (context.isPending) {
53+
return <p>Loading...</p>;
54+
}
55+
56+
if (context.error) {
57+
return <p className="error">{context.error.toString()}</p>;
58+
}
59+
return (
60+
<p>
61+
{listContext.data?.map((tag, index) => (
62+
<li key={index}>{tag.name}</li>
63+
))}
64+
</p>
65+
);
66+
};
67+
68+
export const PostList = () => (
69+
<List>
70+
<DataTable>
71+
<DataTable.Col source="id" />
72+
<DataTable.Col source="title" />
73+
<DataTable.Col source="tag_ids" label="Tags">
74+
<ReferenceArrayFieldBase reference="tags" source="tag_ids">
75+
<MyPostView />
76+
</ReferenceArrayFieldBase>
77+
</DataTable.Col>
78+
<DataTable.Col>
79+
<EditButton />
80+
</DataTable.Col>
81+
</DataTable>
82+
</List>
83+
);
84+
```
85+
86+
`<ReferenceArrayFieldBase>` expects a `reference` attribute, which specifies the resource to fetch for the related records. It also expects a `source` attribute, which defines the field containing the list of ids to look for in the referenced resource.
87+
88+
`<ReferenceArrayFieldBase>` fetches the `tag` resources related to each `post` resource by matching `post.tag_ids` to `tag.id`.
89+
90+
You can change how the list of related records is rendered by passing a custom child reading the `ListContext` (e.g. a [`<DataTable>`](./DataTable.md)). See the [`children`](#children) section for details.
91+
92+
## Props
93+
94+
| Prop | Required | Type | Default | Description |
95+
| -------------- | -------- | --------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------ |
96+
| `source` | Required | `string` | - | Name of the property to display |
97+
| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'tags' |
98+
| `children` | Required if no render | `Element` | | One or several elements that render a list of records based on a `ListContext` |
99+
| `render` | Required if no children | `Element` | | A function that takes a list context and render a list of records |
100+
| `filter` | Optional | `Object` | - | Filters to use when fetching the related records (the filtering is done client-side) |
101+
| `pagination` | Optional | `Element` | - | Pagination element to display pagination controls. empty by default (no pagination) |
102+
| `perPage` | Optional | `number` | 1000 | Maximum number of results to display |
103+
| `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` options for the `getMany` query |
104+
| `sort` | Optional | `{ field, order }` | `{ field: 'id', order: 'DESC' }` | Sort order to use when displaying the related records (the sort is done client-side) |
105+
| `sortBy` | Optional | `string | Function` | `source` | When used in a `List`, name of the field to use for sorting when the user clicks on the column header. |
106+
107+
## `children`
108+
109+
You can pass any component of your own as child, to render the list of related records as you wish.
110+
You can access the list context using the `useListContext` hook.
111+
112+
113+
```jsx
114+
115+
<ReferenceArrayFieldBase label="Tags" reference="tags" source="tag_ids">
116+
<MyPostView />
117+
</ReferenceArrayFieldBase>
118+
119+
// With MyPostView like:
120+
const MyPostView = (props: { children: React.ReactNode }) => {
121+
const context = useListContext();
122+
123+
if (context.isPending) {
124+
return <p>Loading...</p>;
125+
}
126+
127+
if (context.error) {
128+
return <p className="error">{context.error.toString()}</p>;
129+
}
130+
return (
131+
<p>
132+
{listContext.data?.map((tag, index) => (
133+
<li key={index}>{tag.name}</li>
134+
))}
135+
</p>
136+
);
137+
};
138+
```
139+
140+
## `render`
141+
142+
Alternatively to children you can pass a render prop to `<ReferenceArrayFieldBase>`. The render prop will receive the list context as its argument, allowing to inline the render logic for both the list and the pagination.
143+
When receiving a render prop the `<ReferenceArrayFieldBase>` component will ignore the children and the pagination property.
144+
145+
146+
```jsx
147+
<ReferenceArrayFieldBase
148+
label="Tags"
149+
reference="tags"
150+
source="tag_ids"
151+
render={(context) => {
152+
153+
if (context.isPending) {
154+
return <p>Loading...</p>;
155+
}
156+
157+
if (context.error) {
158+
return <p className="error">{context.error.toString()}</p>;
159+
}
160+
return (
161+
<p>
162+
{listContext.data?.map((tag, index) => (
163+
<li key={index}>{tag.name}</li>
164+
))}
165+
</p>
166+
);
167+
}}
168+
/>
169+
```
170+
171+
## `filter`
172+
173+
`<ReferenceArrayFieldBase>` fetches all the related records, and displays them all, too. You can use the `filter` prop to filter the list of related records to display (this works by filtering the records client-side, after the fetch).
174+
175+
For instance, to render only tags that are 'published', you can use the following code:
176+
177+
{% raw %}
178+
```jsx
179+
<ReferenceArrayFieldBase
180+
label="Tags"
181+
source="tag_ids"
182+
reference="tags"
183+
filter={{ is_published: true }}
184+
/>
185+
```
186+
{% endraw %}
187+
188+
## `pagination`
189+
190+
`<ReferenceArrayFieldBase>` fetches *all* the related fields, and puts them all in a `ListContext`. If a record has a large number of related records, you can limit the number of displayed records with the [`perPage`](#perpage) prop. Then, let users display remaining records by rendering pagination controls. For that purpose, pass a pagination element to the `pagination` prop.
191+
192+
For instance, to limit the display of related records to 10, you can use the following code:
193+
194+
```jsx
195+
import { Pagination, ReferenceArrayFieldBase } from 'react-admin';
196+
197+
<ReferenceArrayFieldBase
198+
label="Tags"
199+
source="tag_ids"
200+
reference="tags"
201+
perPage={10}
202+
pagination={<Pagination />}
203+
/>
204+
```
205+
206+
***Note:*** The pagination prop will be ignored when the component receive a render prop
207+
208+
## `perPage`
209+
210+
`<ReferenceArrayFieldBase>` fetches *all* the related fields, and puts them all in a `ListContext`. If a record has a large number of related records, it may be a good idea to limit the number of displayed records. The `perPage` prop allows to create a client-side pagination for the related records.
211+
212+
For instance, to limit the display of related records to 10, you can use the following code:
213+
214+
```jsx
215+
<ReferenceArrayFieldBase label="Tags" source="tag_ids" reference="tags" perPage={10} />
216+
```
217+
218+
If you want to let the user display the remaining records, you have to pass a [`pagination`](#pagination) element.
219+
220+
## `queryOptions`
221+
222+
Use the `queryOptions` prop to pass options to [the `dataProvider.getMany()` query](./useGetOne.md#aggregating-getone-calls) that fetches the referenced record.
223+
224+
For instance, to pass [a custom `meta`](./Actions.md#meta-parameter):
225+
226+
{% raw %}
227+
```jsx
228+
<ReferenceArrayFieldBase queryOptions={{ meta: { foo: 'bar' } }} />
229+
```
230+
{% endraw %}
231+
232+
233+
## `reference`
234+
235+
The resource to fetch for the relateds record.
236+
237+
For instance, if the `posts` resource has a `tag_ids` field, set the `reference` to `tags` to fetch the tags related to each post.
238+
239+
```jsx
240+
<ReferenceArrayFieldBase label="Tags" source="tag_ids" reference="tags" />
241+
```
242+
243+
## `sort`
244+
245+
By default, the related records are displayed in the order in which they appear in the `source`. For instance, if the current record is `{ id: 1234, title: 'Lorem Ipsum', tag_ids: [1, 23, 4] }`, a `<ReferenceArrayFieldBase>` on the `tag_ids` field will display tags in the order 1, 23, 4.
246+
247+
`<ReferenceArrayFieldBase>` can force a different order (via a client-side sort after fetch) if you specify a `sort` prop.
248+
249+
For instance, to sort tags by title in ascending order, you can use the following code:
250+
251+
{% raw %}
252+
```jsx
253+
<ReferenceArrayFieldBase
254+
label="Tags"
255+
source="tag_ids"
256+
reference="tags"
257+
sort={{ field: 'title', order: 'ASC' }}
258+
/>
259+
```
260+
{% endraw %}

docs/ReferenceManyFieldBase.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export const PostList = () => (
102102
103103
| Prop | Required | Type | Default | Description |
104104
| -------------- | -------- | --------------------------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------- |
105-
| `children` | Required if no render | `Element` | - | Element that render a list of records based on a `ListContext` |
105+
| `children` | Required if no render | `Element` | - | One or several elements that render a list of records based on a `ListContext` |
106106
| `render` | Required if no children | `(listContext) => Element` | - | Function that receives a `ListContext` and render elements |
107107
| `debounce` | Optional | `number` | 500 | debounce time in ms for the `setFilters` callbacks |
108108
| `empty` | Optional | `ReactNode` | - | Element to display when there are no related records. |

packages/ra-core/src/controller/field/ReferenceArrayFieldBase.stories.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
ShowBase,
1010
TestMemoryRouter,
1111
useListContext,
12-
useListContextWithProps,
1312
} from '../..';
1413
import { QueryClient } from '@tanstack/react-query';
1514

@@ -181,7 +180,7 @@ const MyReferenceArrayField = (props: { children: React.ReactNode }) => {
181180
};
182181

183182
const List = ({ source }: { source: string }) => {
184-
const listContext = useListContextWithProps();
183+
const listContext = useListContext();
185184
return (
186185
<p>
187186
{listContext.data?.map((datum, index) => (
@@ -192,12 +191,7 @@ const List = ({ source }: { source: string }) => {
192191
};
193192

194193
const Pagination = () => {
195-
const {
196-
page = 1,
197-
setPage,
198-
total = 0,
199-
perPage = 0,
200-
} = useListContextWithProps();
194+
const { page = 1, setPage, total = 0, perPage = 0 } = useListContext();
201195
const nextPage = () => {
202196
setPage?.(page + 1);
203197
};

0 commit comments

Comments
 (0)