|
| 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 | + |
| 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. |
0 commit comments