Skip to content

Commit d385168

Browse files
committed
add doc for ReferenceFieldBase
1 parent 4d1c035 commit d385168

File tree

2 files changed

+297
-5
lines changed

2 files changed

+297
-5
lines changed

docs/ReferenceFieldBase.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
layout: default
3+
title: "The ReferenceFieldBase Component"
4+
storybook_path: ra-core-controller-field-referencefieldbase--basic
5+
---
6+
7+
# `<ReferenceFieldBase>`
8+
9+
`<ReferenceFieldBase>` is useful for displaying many-to-one and one-to-one relationships, e.g. the details of a user when rendering a post authored by that user.
10+
`<ReferenceFieldBase>` is a headless component, handling only the logic. This Allows to plug any UI library on top. For the version incorporating UI see [`<ReferenceFieldBase>`](/ReferenceField.html)
11+
12+
## Usage
13+
14+
### With children
15+
16+
For instance, let's consider a model where a `post` has one author from the `users` resource, referenced by a `user_id` field.
17+
18+
```
19+
┌──────────────┐ ┌────────────────┐
20+
│ posts │ │ users │
21+
│--------------│ │----------------│
22+
│ id │ ┌───│ id │
23+
│ user_id │╾──┘ │ name │
24+
│ title │ │ date_of_birth │
25+
│ published_at │ └────────────────┘
26+
└──────────────┘
27+
```
28+
29+
In that case, use `<ReferenceFieldBase>` to display the post author's as follows:
30+
31+
```jsx
32+
import { Show, SimpleShowLayout, ReferenceField, TextField, DateField } from 'react-admin';
33+
34+
export const PostShow = () => (
35+
<Show>
36+
<SimpleShowLayout>
37+
<TextField source="id" />
38+
<TextField source="title" />
39+
<DateField source="published_at" />
40+
<ReferenceFieldBase source="user_id" reference="users" label="Author">
41+
<CustomUIRenderer />
42+
</ReferenceFieldBase>
43+
</SimpleShowLayout>
44+
</Show>
45+
);
46+
```
47+
48+
`<ReferenceFieldBase>` fetches the data, puts it in a [`RecordContext`](./useRecordContext.md), and its up to its children to handle the rendering by accessing the ReferencingContext using the useReferenceFieldContext hook.
49+
50+
This component fetches a referenced record (`users` in this example) using the `dataProvider.getMany()` method, and passes it to the ReferenceFieldContext.
51+
52+
```tsx
53+
import { Show, SimpleShowLayout, ReferenceField, TextField, DateField } from 'react-admin';
54+
55+
export const MyReferenceFieldView = () => {
56+
const context = useReferenceFieldContext();
57+
58+
const value = useFieldValue({ source });
59+
if (context.isPending) {
60+
return <p>Loading...</p>;
61+
}
62+
63+
if (context.error) {
64+
return <p className="error">{context.error.toString()}</p>;
65+
}
66+
67+
return <p>{value}</p>;
68+
};
69+
70+
export const MyReferenceField = () => (
71+
<ReferenceFieldBase source="user_id" reference="users">
72+
<MyReferenceFieldView />
73+
</ReferenceFieldBase>
74+
);
75+
```
76+
77+
It uses `dataProvider.getMany()` instead of `dataProvider.getOne()` [for performance reasons](#performance). When using several `<ReferenceFieldBase>` in the same page (e.g. in a `<DataTable>`), this allows to call the `dataProvider` once instead of once per row.
78+
79+
### With render prop
80+
81+
Alternatively you can pass a render prop instead of children to be able to inline the rendering. The render function will then receive the reference field context directly.
82+
83+
```jsx
84+
export const MyReferenceField = () => (
85+
<ReferenceFieldBase source="user_id" reference="users" render={({ error, isPending }) => {
86+
if (isPending) {
87+
return <p>Loading...</p>;
88+
}
89+
90+
if (error) {
91+
return (
92+
<p className="error">
93+
{error.message}
94+
</p>
95+
);
96+
}
97+
return <p>{value}</p>;
98+
}} />
99+
);
100+
```
101+
102+
## Props
103+
104+
| Prop | Required | Type | Default | Description |
105+
| ----------- | -------- | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
106+
| `source` | Required | `string` | - | Name of the property to display |
107+
| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'posts' |
108+
| `children` | Optional | `ReactNode` | - | React component to render the referenced record, the component need to use useReferenceFieldContext to access the context. |
109+
| `render` | Optional | `(context) => ReactNode` | - | Function that takes the referenceFieldContext and render the referenced record. Will take priority on children props if both are set. |
110+
| `empty` | Optional | `ReactNode` | - | What to render when the field has no value or when the reference is missing |
111+
| `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
112+
| `sortBy` | Optional | `string | Function` | `source` | Name of the field to use for sorting when used in a Datagrid |
113+
114+
115+
## `empty`
116+
117+
`<ReferenceFieldBase>` can display a custom message when the referenced record is missing, thanks to the `empty` prop.
118+
119+
```jsx
120+
<ReferenceFieldBase source="user_id" reference="users" empty="Missing user" />
121+
```
122+
123+
`<ReferenceFieldBase>` renders the `empty` element when:
124+
125+
- the referenced record is missing (no record in the `users` table with the right `user_id`), or
126+
- the field is empty (no `user_id` in the record).
127+
128+
You can pass either a React element or a string to the `empty` prop:
129+
130+
```jsx
131+
<ReferenceField source="user_id" reference="users" empty={<span>Missing user</span>} />
132+
<ReferenceField source="user_id" reference="users" empty="Missing user" />
133+
```
134+
135+
136+
## `link`
137+
138+
To change the link from the `<Edit>` page to the `<Show>` page, set the `link` prop to "show".
139+
140+
```jsx
141+
<ReferenceFieldBase source="user_id" reference="users" link="show" />
142+
```
143+
144+
You can also prevent `<ReferenceFieldBase>` from adding a link to children by setting `link` to `false`.
145+
146+
```jsx
147+
// No link
148+
<ReferenceFieldBase source="user_id" reference="users" link={false} />
149+
```
150+
151+
You can also use a custom `link` function to get a custom path for the children. This function must accept `record` and `reference` as arguments.
152+
153+
```jsx
154+
// Custom path
155+
<ReferenceFieldBase
156+
source="user_id"
157+
reference="users"
158+
link={(record, reference) => `/my/path/to/${reference}/${record.id}`}
159+
/>
160+
```
161+
162+
## `queryOptions`
163+
164+
Use the `queryOptions` prop to pass options to [the `dataProvider.getMany()` query](./useGetOne.md#aggregating-getone-calls) that fetches the referenced record.
165+
166+
For instance, to pass [a custom `meta`](./Actions.md#meta-parameter):
167+
168+
{% raw %}
169+
```jsx
170+
<ReferenceFieldBase
171+
source="user_id"
172+
reference="users"
173+
queryOptions={{ meta: { foo: 'bar' } }}
174+
>
175+
<TextField source="name" />
176+
</ReferenceFieldBase>
177+
```
178+
{% endraw %}
179+
180+
## `reference`
181+
182+
The resource to fetch for the related record.
183+
184+
For instance, if the `posts` resource has a `user_id` field, set the `reference` to `users` to fetch the user related to each post.
185+
186+
```jsx
187+
<ReferenceFieldBase source="user_id" reference="users" />
188+
```
189+
190+
## `sortBy`
191+
192+
By default, when used in a `<Datagrid>`, and when the user clicks on the column header of a `<ReferenceFieldBase>`, react-admin sorts the list by the field `source`. To specify another field name to sort by, set the `sortBy` prop.
193+
194+
```jsx
195+
<ReferenceFieldBase source="user_id" reference="users" sortBy="user.name" />
196+
```
197+
198+
## Performance
199+
200+
<iframe src="https://www.youtube-nocookie.com/embed/egBhWqF3sWc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="aspect-ratio: 16 / 9;width:100%;margin-bottom:1em;"></iframe>
201+
202+
When used in a `<DataTable>`, `<ReferenceFieldBase>` fetches the referenced record only once for the entire table.
203+
204+
![ReferenceField](./img/reference-field.png)
205+
206+
For instance, with this code:
207+
208+
```jsx
209+
import { List, DataTable, ReferenceField, EditButton } from 'react-admin';
210+
211+
export const PostList = () => (
212+
<List>
213+
<DataTable>
214+
<DataTable.Col source="id" />
215+
<DataTable.Col label="User" source="user_id">
216+
<ReferenceField source="user_id" reference="users" />
217+
</DataTable.Col>
218+
<DataTable.Col source="title" />
219+
<DataTable.Col>
220+
<EditButton />
221+
</DataTable.Col>
222+
</DataTable>
223+
</List>
224+
);
225+
```
226+
227+
React-admin accumulates and deduplicates the ids of the referenced records to make *one* `dataProvider.getMany()` call for the entire list, instead of n `dataProvider.getOne()` calls. So for instance, if the API returns the following list of posts:
228+
229+
```js
230+
[
231+
{
232+
id: 123,
233+
title: 'Totally agree',
234+
user_id: 789,
235+
},
236+
{
237+
id: 124,
238+
title: 'You are right my friend',
239+
user_id: 789
240+
},
241+
{
242+
id: 125,
243+
title: 'Not sure about this one',
244+
user_id: 735
245+
}
246+
]
247+
```
248+
249+
Then react-admin renders the `<PostList>` with a loader for the `<ReferenceFieldBase>`, fetches the API for the related users in one call (`dataProvider.getMany('users', { ids: [789,735] }`), and re-renders the list once the data arrives. This accelerates the rendering and minimizes network load.
250+
251+
## Prefetching
252+
253+
When you know that a page will contain a `<ReferenceFieldBase>`, you can configure the main page query to prefetch the referenced records to avoid a flicker when the data arrives. To do so, pass a `meta.prefetch` parameter to the page query.
254+
255+
For example, the following code prefetches the authors referenced by the posts:
256+
257+
{% raw %}
258+
```jsx
259+
const PostList = () => (
260+
<List queryOptions={{ meta: { prefetch: ['author'] } }}>
261+
<DataTable>
262+
<DataTable.Col source="title" />
263+
<DataTable.Col source="author_id">
264+
{/** renders without an additional request */}
265+
<ReferenceFieldBase source="author_id" reference="authors" />
266+
</DataTable.Col>
267+
</DataTable>
268+
</List>
269+
);
270+
```
271+
{% endraw %}
272+
273+
**Note**: For prefetching to function correctly, your data provider must support [Prefetching Relationships](./DataProviders.md#prefetching-relationships). Refer to your data provider's documentation to verify if this feature is supported.
274+
275+
**Note**: Prefetching is a frontend performance feature, designed to avoid flickers and repaints. It doesn't always prevent `<ReferenceFieldBase>` to fetch the data. For instance, when coming to a show view from a list view, the main record is already in the cache, so the page renders immediately, and both the page controller and the `<ReferenceFieldBase>` controller fetch the data in parallel. The prefetched data from the page controller arrives after the first render of the `<ReferenceFieldBase>`, so the data provider fetches the related data anyway. But from a user perspective, the page displays immediately, including the `<ReferenceFieldBase>`. If you want to avoid the `<ReferenceFieldBase>` to fetch the data, you can use the React Query Client's `staleTime` option.
276+
277+
## Access Control
278+
279+
If your authProvider implements [the `canAccess` method](./AuthProviderWriting.md#canaccess), React-Admin will verify whether users have access to the Show and Edit views.
280+
281+
For instance, given the following `ReferenceFieldBase`:
282+
283+
```jsx
284+
<ReferenceFieldBase source="user_id" reference="users" />
285+
```
286+
287+
React-Admin will call `canAccess` with the following parameters:
288+
- If the `users` resource has a Show view: `{ action: "show", resource: 'posts', record: Object }`
289+
- If the `users` resource has an Edit view: `{ action: "edit", resource: 'posts', record: Object }`
290+
291+
And the link property of the referenceField context will be set accordingly. It will be set to false if the access is denied.

packages/ra-core/src/controller/field/ReferenceFieldBase.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,25 @@ import { useFieldValue } from '../../util';
1818
* added as <Admin> child.
1919
*
2020
* @example // using recordRepresentation
21-
* <ReferenceFieldBase label="User" source="userId" reference="users" />
21+
* <ReferenceFieldBase source="userId" reference="users" />
2222
*
2323
* @example // using a Field component to represent the record
24-
* <ReferenceFieldBase label="User" source="userId" reference="users">
24+
* <ReferenceFieldBase source="userId" reference="users">
2525
* <TextField source="name" />
2626
* </ReferenceFieldBase>
2727
*
2828
* @example // By default, includes a link to the <Edit> page of the related record
2929
* // (`/users/:userId` in the previous example).
3030
* // Set the `link` prop to "show" to link to the <Show> page instead.
31-
* <ReferenceFieldBase label="User" source="userId" reference="users" link="show" />
31+
* <ReferenceFieldBase source="userId" reference="users" link="show" />
3232
*
3333
* @example // You can also prevent `<ReferenceFieldBase>` from adding link to children
3434
* // by setting `link` to false.
35-
* <ReferenceFieldBase label="User" source="userId" reference="users" link={false} />
35+
* <ReferenceFieldBase source="userId" reference="users" link={false} />
3636
*
3737
* @example // Alternatively, you can also pass a custom function to `link`.
3838
* // It must take reference and record as arguments and return a string
39-
* <ReferenceFieldBase label="User" source="userId" reference="users" link={(record, reference) => "/path/to/${reference}/${record}"} />
39+
* <ReferenceFieldBase source="userId" reference="users" link={(record, reference) => "/path/to/${reference}/${record}"} />
4040
*
4141
* @default
4242
* In previous versions of React-Admin, the prop `linkType` was used. It is now deprecated and replaced with `link`. However
@@ -70,6 +70,7 @@ export const ReferenceFieldBase = <
7070
) {
7171
return empty;
7272
}
73+
7374
return (
7475
<ResourceContextProvider value={props.reference}>
7576
<ReferenceFieldContextProvider value={controllerProps}>

0 commit comments

Comments
 (0)