diff --git a/docs/CreateBase.md b/docs/CreateBase.md
index 6edae3d4d9d..c7a4f393237 100644
--- a/docs/CreateBase.md
+++ b/docs/CreateBase.md
@@ -46,6 +46,7 @@ export const BookCreate = () => (
You can customize the `` component using the following props, documented in the `` component:
* `children`: the components that renders the form
+* `render`: alternative to children, a function that takes the `CreateController` context and renders the form
* [`disableAuthentication`](./Create.md#disableauthentication): disable the authentication check
* [`mutationMode`](./Create.md#mutationmode): Switch to optimistic or undoable mutations (pessimistic by default)
* [`mutationOptions`](./Create.md#mutationoptions): options for the `dataProvider.create()` call
diff --git a/docs/EditBase.md b/docs/EditBase.md
index 0d820c6a258..a5671f6f1f1 100644
--- a/docs/EditBase.md
+++ b/docs/EditBase.md
@@ -47,6 +47,7 @@ export const BookEdit = () => (
You can customize the `` component using the following props, documented in the `` component:
* `children`: the components that renders the form
+* `render`: alternative to children, a function that takes the `EditController` context and renders the form
* [`disableAuthentication`](./Edit.md#disableauthentication): disable the authentication check
* [`id`](./Edit.md#id): the id of the record to edit
* [`mutationMode`](./Edit.md#mutationmode): switch to optimistic or pessimistic mutations (undoable by default)
diff --git a/docs/ListBase.md b/docs/ListBase.md
index 05770584c4e..b23d972f27e 100644
--- a/docs/ListBase.md
+++ b/docs/ListBase.md
@@ -6,7 +6,7 @@ storybook_path: ra-core-controller-list-listbase--no-auth-provider
# ``
-`` is a headless variant of [``](./List.md). It fetches a list of records from the data provider, puts it in a [`ListContext`](./useListContext.md), and renders its children. Use it to build a custom list layout.
+`` is a headless List page component. It fetches a list of records from the data provider, puts it in a [`ListContext`](./useListContext.md), and renders its children. Use it to build a custom list layout.
Contrary to [``](./List.md), it does not render the page layout, so no title, no actions, no ``, and no pagination.
@@ -14,47 +14,73 @@ Contrary to [``](./List.md), it does not render the page layout, so no tit
## Usage
-You can use `ListBase` to create your own custom reusable List component, like this one:
+You can use `ListBase` to create your own custom List page component, like this one:
```jsx
import {
+ DataTable,
ListBase,
- Title,
ListToolbar,
- Pagination,
DataTable,
+ Pagination,
+ Title,
} from 'react-admin';
import { Card } from '@mui/material';
-const MyList = ({ children, actions, filters, title, ...props }) => (
-
-
+const PostList = () => (
+
+
- {children}
+
+
+
+
+
);
+```
+
+Alternatively, you can pass a `render` function prop instead of `children`. This function will receive the `ListContext` as argument.
+```jsx
const PostList = () => (
-
-
- ...
-
-
+ (
+
+
+
+
+ {data?.map(record => (
+
+
+
+
+
+ ))}
+
+
+
+ )} />
);
```
-This custom List component has no aside component - it's up to you to add it in pure React.
-
## Props
-The `` component accepts the same props as [`useListController`](./useListController.md):
+The `` component accepts the following props:
+* `children`
* [`debounce`](./List.md#debounce)
* [`disableAuthentication`](./List.md#disableauthentication)
* [`disableSyncWithLocation`](./List.md#disablesyncwithlocation)
@@ -63,13 +89,13 @@ The `` component accepts the same props as [`useListController`](./use
* [`filterDefaultValues`](./List.md#filterdefaultvalues)
* [`perPage`](./List.md#perpage)
* [`queryOptions`](./List.md#queryoptions)
+* `render`
* [`resource`](./List.md#resource)
* [`sort`](./List.md#sort)
-These are a subset of the props accepted by `` - only the props that change data fetching, and not the props related to the user interface.
-
In addition, `` renders its children components inside a `ListContext`. Check [the `` documentation](./List.md#children) for usage examples.
+
## Security
The `` component requires authentication and will redirect anonymous users to the login page. If you want to allow anonymous access, use the [`disableAuthentication`](./List.md#disableauthentication) prop.
diff --git a/docs/ReferenceArrayFieldBase.md b/docs/ReferenceArrayFieldBase.md
new file mode 100644
index 00000000000..92c3a02f7f0
--- /dev/null
+++ b/docs/ReferenceArrayFieldBase.md
@@ -0,0 +1,226 @@
+---
+layout: default
+title: "The ReferenceArrayFieldBase Component"
+storybook_path: ra-core-fields-referencearrayfieldbase--basic
+---
+
+# ``
+
+Use `` to display a list of related records, via a one-to-many relationship materialized by an array of foreign keys.
+
+`` 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.
+
+**Tip**: For a rendering a list of chips by default, use [the `` component](./ReferenceArrayField.md) instead.
+
+**Tip**: If the relationship is materialized by a foreign key on the referenced resource, use [the `` component](./ReferenceManyFieldBase.md) instead.
+
+## Usage
+
+For instance, let's consider a model where a `post` has many `tags`, materialized to a `tags_ids` field containing an array of ids:
+
+```
+┌──────────────┐ ┌────────┐
+│ posts │ │ tags │
+│--------------│ │--------│
+│ id │ ┌───│ id │
+│ title │ │ │ name │
+│ body │ │ └────────┘
+│ is_published │ │
+│ tag_ids │╾──┘
+└──────────────┘
+```
+
+A typical `post` record therefore looks like this:
+
+```json
+{
+ "id": 1,
+ "title": "Hello world",
+ "body": "...",
+ "is_published": true,
+ "tags_ids": [1, 2, 3]
+}
+```
+
+In that case, use `` to display the post tag names as a list of chips, as follows:
+
+```jsx
+import { ListBase, ListIterator, ReferenceArrayFieldBase } from 'react-admin';
+
+export const PostList = () => (
+
+
+
+
+
+
+
+);
+
+const TagList = (props: { children: React.ReactNode }) => {
+ const context = useListContext();
+
+ if (context.isPending) {
+ return
Loading...
;
+ }
+
+ if (context.error) {
+ return
{context.error.toString()}
;
+ }
+ return (
+
+ {listContext.data?.map((tag, index) => (
+
{tag.name}
+ ))}
+
+ );
+};
+```
+
+`` 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.
+
+`` fetches the `tag` resources related to each `post` resource by matching `post.tag_ids` to `tag.id`.
+
+You can change how the list of related records is rendered by passing a custom child reading the `ListContext` (e.g. a [``](./DataTable.md)) or a render function prop. See the [`children`](#children) and the [`render`](#render) sections for details.
+
+## Props
+
+| Prop | Required | Type | Default | Description |
+| -------------- | -------- | --------------------------------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------ |
+| `source` | Required | `string` | - | Name of the property to display |
+| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'tags' |
+| `children` | Optional\* | `Element` | | One or several elements that render a list of records based on a `ListContext` |
+| `render` | Optional\* | `(ListContext) => Element` | | A function that takes a list context and renders a list of records |
+| `filter` | Optional | `Object` | - | Filters to use when fetching the related records (the filtering is done client-side) |
+| `perPage` | Optional | `number` | 1000 | Maximum number of results to display |
+| `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` options for the `getMany` query |
+| `sort` | Optional | `{ field, order }` | `{ field: 'id', order: 'DESC' }` | Sort order to use when displaying the related records (the sort is done client-side) |
+| `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. |
+
+\* Either one of children or render is required.
+
+## `children`
+
+You can pass any React component as child, to render the list of related records based on the `ListContext`.
+
+```jsx
+
+
+
+
+const TagList = (props: { children: React.ReactNode }) => {
+ const { isPending, error, data } = useListContext();
+
+ if (isPending) {
+ return
Loading...
;
+ }
+
+ if (error) {
+ return
{error.toString()}
;
+ }
+ return (
+
+ {data?.map((tag, index) => (
+
{tag.name}
+ ))}
+
+ );
+};
+```
+
+## `render`
+
+Alternatively to `children`, you can pass a `render` function prop to ``. The `render` prop will receive the `ListContext` as its argument, allowing to inline the rendering logic.
+
+```jsx
+ {
+ if (isPending) {
+ return
Loading...
;
+ }
+ if (error) {
+ return
{error.toString()}
;
+ }
+ return (
+
+ {data.map((tag, index) => (
+
{tag.name}
+ ))}
+
+ );
+ }}
+/>
+```
+
+**Tip**: When receiving a `render` prop, the `` component will ignore the `children` property.
+
+## `filter`
+
+`` 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).
+
+For instance, to render only tags that are 'published', you can use the following code:
+
+{% raw %}
+```jsx
+
+```
+{% endraw %}
+
+## `perPage`
+
+`` 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.
+
+For instance, to limit the display of related records to 10, you can use the following code:
+
+```jsx
+
+```
+
+## `queryOptions`
+
+Use the `queryOptions` prop to pass options to [the `dataProvider.getMany()` query](./useGetOne.md#aggregating-getone-calls) that fetches the referenced record.
+
+For instance, to pass [a custom `meta`](./Actions.md#meta-parameter):
+
+{% raw %}
+```jsx
+
+```
+{% endraw %}
+
+## `reference`
+
+The resource to fetch for the relateds record.
+
+For instance, if the `posts` resource has a `tag_ids` field, set the `reference` to `tags` to fetch the tags related to each post.
+
+```jsx
+
+```
+
+## `sort`
+
+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 `` on the `tag_ids` field will display tags in the order 1, 23, 4.
+
+`` can force a different order (via a client-side sort after fetch) if you specify a `sort` prop.
+
+For instance, to sort tags by title in ascending order, you can use the following code:
+
+{% raw %}
+```jsx
+
+```
+{% endraw %}
diff --git a/docs/ReferenceFieldBase.md b/docs/ReferenceFieldBase.md
new file mode 100644
index 00000000000..d2c7695092e
--- /dev/null
+++ b/docs/ReferenceFieldBase.md
@@ -0,0 +1,288 @@
+---
+layout: default
+title: "The ReferenceFieldBase Component"
+storybook_path: ra-core-controller-field-referencefieldbase--basic
+---
+
+# ``
+
+`` 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.
+`` is a headless component, handling only the logic. This allows to use any UI library for the render. For a version based on MUI see [``](/ReferenceField.html)
+
+## Usage
+
+For instance, let's consider a model where a `post` has one author from the `users` resource, referenced by a `user_id` field.
+
+```
+┌──────────────┐ ┌────────────────┐
+│ posts │ │ users │
+│--------------│ │----------------│
+│ id │ ┌───│ id │
+│ user_id │╾──┘ │ name │
+│ title │ │ date_of_birth │
+│ published_at │ └────────────────┘
+└──────────────┘
+```
+
+In that case, use `` to display the post's author as follows:
+
+```jsx
+import { Show, SimpleShowLayout, ReferenceField, TextField, RecordRepresentation } from 'react-admin';
+
+export const PostShow = () => (
+
+
+
+
+
+
+
+
+);
+
+export const UserView = () => {
+ const context = useReferenceFieldContext();
+
+ if (context.isPending) {
+ return
Loading...
;
+ }
+
+ if (context.error) {
+ return
{context.error.toString()}
;
+ }
+
+ return ;
+};
+```
+
+`` 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.
+
+It uses `dataProvider.getMany()` instead of `dataProvider.getOne()` [for performance reasons](#performance). When using several `` in the same page (e.g. in a ``), this allows to call the `dataProvider` once instead of once per row.
+
+## Props
+
+| Prop | Required | Type | Default | Description |
+| ----------- | -------- | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
+| `source` | Required | `string` | - | Name of the property to display |
+| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'posts' |
+| `children` | Optional | `ReactNode` | - | React component to render the referenced record. |
+| `render` | Optional | `(context) => ReactNode` | - | Function that takes the referenceFieldContext and renders the referenced record. |
+| `empty` | Optional | `ReactNode` | - | What to render when the field has no value or when the reference is missing |
+| `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
+| `sortBy` | Optional | `string | Function` | `source` | Name of the field to use for sorting when used in a Datagrid |
+
+## `children`
+
+You can pass any component of your own as child, to render the related records as you wish.
+You can access the list context using the `useReferenceFieldContext` hook.
+
+```tsx
+import { ReferenceFieldBase } from 'react-admin';
+
+export const UserView = () => {
+ const { error, isPending, referenceRecord } = useReferenceFieldContext();
+
+ if (isPending) {
+ return
Loading...
;
+ }
+
+ if (error) {
+ return
{error.toString()}
;
+ }
+
+ return <>{referenceRecord.name}>;
+};
+
+export const MyReferenceField = () => (
+
+
+
+);
+```
+
+## `render`
+
+Alternatively, you can pass a `render` function prop instead of children. This function will receive the `ReferenceFieldContext` as argument.
+
+```jsx
+export const MyReferenceField = () => (
+ {
+ if (isPending) {
+ return
Loading...
;
+ }
+
+ if (error) {
+ return (
+
+ {error.message}
+
+ );
+ }
+ return
{referenceRecord.name}
;
+ }}
+ />
+);
+```
+
+The `render` function prop will take priority on `children` props if both are set.
+
+## `empty`
+
+`` can display a custom message when the referenced record is missing, thanks to the `empty` prop.
+
+```jsx
+
+ ...
+
+```
+
+`` renders the `empty` element when:
+
+- the referenced record is missing (no record in the `users` table with the right `user_id`), or
+- the field is empty (no `user_id` in the record).
+
+You can pass either a React element or a string to the `empty` prop:
+
+```jsx
+Missing user} >
+ ...
+
+
+ ...
+
+```
+
+## `queryOptions`
+
+Use the `queryOptions` prop to pass options to [the `dataProvider.getMany()` query](./useGetOne.md#aggregating-getone-calls) that fetches the referenced record.
+
+For instance, to pass [a custom `meta`](./Actions.md#meta-parameter):
+
+{% raw %}
+```jsx
+ referenceRecord.name}
+>
+ ...
+
+```
+{% endraw %}
+
+## `reference`
+
+The resource to fetch for the related record.
+
+For instance, if the `posts` resource has a `user_id` field, set the `reference` to `users` to fetch the user related to each post.
+
+```jsx
+
+ ...
+
+```
+
+## `sortBy`
+
+By default, when used in a ``, and when the user clicks on the column header of a ``, react-admin sorts the list by the field `source`. To specify another field name to sort by, set the `sortBy` prop.
+
+```jsx
+
+ ...
+
+```
+
+## Performance
+
+
+
+When used in a ``, `` fetches the referenced record only once for the entire table.
+
+For instance, with this code:
+
+```jsx
+import { ListBase, ListIterator, ReferenceFieldBase } from 'react-admin';
+
+export const PostList = () => (
+
+
+
+
+
+
+
+);
+```
+
+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:
+
+```js
+[
+ {
+ id: 123,
+ title: 'Totally agree',
+ user_id: 789,
+ },
+ {
+ id: 124,
+ title: 'You are right my friend',
+ user_id: 789
+ },
+ {
+ id: 125,
+ title: 'Not sure about this one',
+ user_id: 735
+ }
+]
+```
+
+Then react-admin renders the `` with a loader for the ``, 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.
+
+## Prefetching
+
+When you know that a page will contain a ``, 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.
+
+For example, the following code prefetches the authors referenced by the posts:
+
+{% raw %}
+```jsx
+const PostList = () => (
+
+ (
+
+
{title}
+
+
+
+
+ )}
+ />
+
+);
+```
+{% endraw %}
+
+**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.
+
+**Note**: Prefetching is a frontend performance feature, designed to avoid flickers and repaints. It doesn't always prevent `` 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 `` controller fetch the data in parallel. The prefetched data from the page controller arrives after the first render of the ``, so the data provider fetches the related data anyway. But from a user perspective, the page displays immediately, including the ``. If you want to avoid the `` to fetch the data, you can use the React Query Client's `staleTime` option.
+
+## Access Control
+
+If your authProvider implements [the `canAccess` method](./AuthProviderWriting.md#canaccess), React-Admin will verify whether users have access to the Show and Edit views.
+
+For instance, given the following `ReferenceFieldBase`:
+
+```jsx
+
+```
+
+React-Admin will call `canAccess` with the following parameters:
+- If the `users` resource has a Show view: `{ action: "show", resource: 'posts', record: Object }`
+- If the `users` resource has an Edit view: `{ action: "edit", resource: 'posts', record: Object }`
+
+And the link property of the referenceField context will be set accordingly. It will be set to false if the access is denied.
\ No newline at end of file
diff --git a/docs/ReferenceManyFieldBase.md b/docs/ReferenceManyFieldBase.md
new file mode 100644
index 00000000000..646bf008f98
--- /dev/null
+++ b/docs/ReferenceManyFieldBase.md
@@ -0,0 +1,360 @@
+---
+layout: default
+title: "The ReferenceManyFieldBase Component"
+storybook_path: ra-core-controller-field-referencemanyfieldbase--basic
+---
+
+# ``
+
+`` is useful for displaying a list of related records via a one-to-many relationship, when the foreign key is carried by the referenced resource.
+
+This component fetches a list of referenced records by a reverse lookup of the current `record.id` in the `target` field of another resource (using the `dataProvider.getManyReference()` REST method), and puts them in a [`ListContext`](./useListContext.md).
+
+This component is headless. It relies on its `children` or a `render` prop to render the desired ui.
+
+**Tip**: If the relationship is materialized by an array of ids in the initial record, use [the `` component](./ReferenceArrayFieldBase.md) instead.
+
+## Usage
+
+For instance, if an `author` has many `books`, and each book resource exposes an `author_id` field:
+
+```
+┌────────────────┐ ┌──────────────┐
+│ authors │ │ books │
+│----------------│ │--------------│
+│ id │───┐ │ id │
+│ first_name │ └──╼│ author_id │
+│ last_name │ │ title │
+│ date_of_birth │ │ published_at │
+└────────────────┘ └──────────────┘
+```
+
+`` can render the titles of all the books by a given author.
+
+```jsx
+import { ShowBase, ReferenceManyFieldBase } from 'react-admin';
+
+const AuthorShow = () => (
+
+
+
+
+
+);
+
+const BookList = ({
+ source,
+ children,
+}: {
+ source: string;
+}) => {
+ const context = useListContext();
+
+ if (context.isPending) {
+ return
Loading...
;
+ }
+
+ if (context.error) {
+ return
{context.error.toString()}
;
+ }
+ return (
+
+ {listContext.data?.map((book, index) => (
+
{book[source]}
+ ))}
+
+ );
+};
+```
+
+`` accepts a `reference` attribute, which specifies the resource to fetch for the related record. It also accepts a `source` attribute which defines the field containing the value to look for in the `target` field of the referenced resource. By default, this is the `id` of the resource (`authors.id` in the previous example).
+
+You can also use `` in a list, e.g. to display the authors of the comments related to each post in a list by matching `post.id` to `comment.post_id`:
+
+```jsx
+import { ListBase, ListIterator, ReferenceManyFieldBase } from 'react-admin';
+
+export const PostList = () => (
+
+
+
+
+
+
+
+);
+```
+
+## Props
+
+| Prop | Required | Type | Default | Description |
+| -------------- | -------- | --------------------------------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------- |
+| `children` | Optional | `Element` | - | One or several elements that render a list of records based on a `ListContext` |
+| `render` | Optional\* | `(ListContext) => Element` | - | Function that receives a `ListContext` and returns an element |
+| `debounce` | Optional\* | `number` | 500 | debounce time in ms for the `setFilters` callbacks |
+| `empty` | Optional | `ReactNode` | - | Element to display when there are no related records. |
+| `filter` | Optional | `Object` | - | Filters to use when fetching the related records, passed to `getManyReference()` |
+| `perPage` | Optional | `number` | 25 | Maximum number of referenced records to fetch |
+| `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v3/docs/react/reference/useQuery) | `{}` | `react-query` options for the `getMany` query |
+| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'books' |
+| `sort` | Optional | `{ field, order }` | `{ field: 'id', order: 'DESC' }` | Sort order to use when fetching the related records, passed to `getManyReference()` |
+| `source` | Optional | `string` | `id` | Target field carrying the relationship on the source record (usually 'id') |
+| `storeKey` | Optional | `string` | - | The key to use to store the records selection state |
+| `target` | Required | `string` | - | Target field carrying the relationship on the referenced resource, e.g. 'user_id' |
+
+\* Either one of children or render is required.
+
+## `children`
+
+`` renders its children inside a [`ListContext`](./useListContext.md). This means you can use any component that uses a `ListContext`:
+
+- [``](./SingleFieldList.md)
+- [``](./DataTable.md)
+- [``](./Datagrid.md)
+- [``](./SimpleList.md)
+- [``](./EditableDatagrid.md)
+- [``](./Calendar.md)
+
+For instance, use a `` to render the related records:
+
+```jsx
+import { ShowBase, ReferenceManyFieldBase, ListIterator } from 'react-admin';
+
+export const AuthorShow = () => (
+
+
+
+ (
+
+ {book.title}, published on{' '}{book.published_at}
+
+ )}/>
+
+
+
+);
+```
+
+## `render`
+
+Alternatively, you can pass a `render` function prop instead of children. The `render` prop will receive the `ListContext` as arguments, allowing to inline the render logic.
+When receiving a `render` function prop the `` component will ignore the children property.
+
+```jsx
+import { ShowBase, ReferenceManyFieldBase } from 'react-admin';
+
+const AuthorShow = () => (
+
+ {
+
+ if (isPending) {
+ return
Loading...
;
+ }
+
+ if (error) {
+ return
{error.toString()}
;
+ }
+ return (
+
+ {data.map((book, index) => (
+
+ {book.title}, published on{' '}{book.published_at}
+
+ ))}
+
+ );
+ }
+ }
+ />
+
+);
+```
+
+## `debounce`
+
+By default, `` 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.
+
+You can customize the debounce duration in milliseconds - or disable it completely - by passing a `debounce` prop to the `` component:
+
+```jsx
+// wait 1 seconds instead of 500 milliseconds before calling the dataProvider
+const PostCommentsField = () => (
+
+ ...
+
+);
+```
+
+## `empty`
+
+Use `empty` to customize the text displayed when the related record is empty.
+
+```jsx
+
+ ...
+
+```
+
+`empty` also accepts a translation key.
+
+```jsx
+
+ ...
+
+```
+
+`empty` also accepts a `ReactNode`.
+
+```jsx
+Create}
+>
+ ...
+
+```
+
+## `filter`: Permanent Filter
+
+You can filter the query used to populate the possible values. Use the `filter` prop for that.
+
+{% raw %}
+
+```jsx
+
+ ...
+
+```
+
+{% endraw %}
+
+## `perPage`
+
+By default, react-admin restricts the possible values to 25 and displays no pagination control. You can change the limit by setting the `perPage` prop:
+
+```jsx
+
+ ...
+
+```
+
+## `queryOptions`
+
+Use the `queryOptions` prop to pass options to [the `dataProvider.getMany()` query](./useGetOne.md#aggregating-getone-calls) that fetches the referenced record.
+
+For instance, to pass [a custom `meta`](./Actions.md#meta-parameter):
+
+{% raw %}
+```jsx
+
+ ...
+
+```
+{% endraw %}
+
+## `reference`
+
+The name of the resource to fetch for the related records.
+
+For instance, if you want to display the `books` of a given `author`, the `reference` name should be `books`:
+
+```jsx
+
+ ...
+
+```
+
+## `sort`
+
+By default, it orders the possible values by id desc. You can change this order by setting the `sort` prop (an object with `field` and `order` properties).
+
+{% raw %}
+```jsx
+
+ ...
+
+```
+{% endraw %}
+
+## `source`
+
+By default, `ReferenceManyFieldBase` uses the `id` field as target for the reference. If the foreign key points to another field of your record, you can select it with the `source` prop.
+
+```jsx
+
+ ...
+
+```
+
+## `storeKey`
+
+By default, react-admin stores the reference list selection state in localStorage so that users can come back to the list and find it in the same state as when they left it. React-admin uses the main resource, record id and reference resource as the identifier to store the selection state (under the key `${resource}.${record.id}.${reference}.selectedIds`).
+
+If you want to display multiple lists of the same reference and keep distinct selection states for each one, you must give each list a unique `storeKey` property.
+
+In the example below, both lists use the same reference ('books'), but their selection states are stored separately (under the store keys `'authors.1.books.selectedIds'` and `'custom.selectedIds'` respectively). This allows to use both components in the same page, each having its own state.
+
+{% raw %}
+```jsx
+
+
+ (
+
{book.title}
+ )} />
+
+
+ (
+
{book.title}
+ )} />
+
+
+```
+{% endraw %}
+
+## `target`
+
+Name of the field carrying the relationship on the referenced resource. For instance, if an `author` has many `books`, and each book resource exposes an `author_id` field, the `target` would be `author_id`.
+
+```jsx
+
+ ...
+
+```
diff --git a/docs/ReferenceOneFieldBase.md b/docs/ReferenceOneFieldBase.md
new file mode 100644
index 00000000000..0f119d04502
--- /dev/null
+++ b/docs/ReferenceOneFieldBase.md
@@ -0,0 +1,305 @@
+---
+layout: default
+title: "The ReferenceOneFieldBase Component"
+storybook_path: ra-ui-materialui-fields-referenceonefieldbase--basic
+---
+
+# ``
+
+This field fetches a one-to-one relationship, e.g. the details of a book, when using a foreign key on the distant resource.
+
+```
+┌──────────────┐ ┌──────────────┐
+│ books │ │ book_details │
+│--------------│ │--------------│
+│ id │───┐ │ id │
+│ title │ └──╼│ book_id │
+│ published_at │ │ genre │
+└──────────────┘ │ ISBN │
+ └──────────────┘
+```
+
+`` behaves like ``: it uses the current `record` (a book in this example) to build a filter for the book details with the foreign key (`book_id`). Then, it uses `dataProvider.getManyReference('book_details', { target: 'book_id', id: book.id })` to fetch the related details, and takes the first one.
+
+`` is a headless component, handling only the logic and relying on its `children` or `render` prop to render the UI.
+
+**Tip**: For a version based on MUI, see [``](/ReferenceOneField.html)
+
+**Tip**: For the inverse relationships (the book linked to a book_detail), you can use a [``](./ReferenceFieldBase.md).
+
+## Usage
+
+Here is how to render a field of the `book_details` resource inside a Show view for the `books` resource:
+
+```jsx
+const BookShow = () => (
+
+
+
+
+
+);
+
+const BookDetails = () => {
+ const context = useReferenceFieldContext({
+ reference,
+ target,
+ });
+
+ if (context.isPending) {
+ return
Loading...
;
+ }
+
+ if (context.error) {
+ return
{context.error.toString()}
;
+ }
+ if (!context.referenceRecord) {
+ return
No details found
;
+ }
+ return (
+
+
{context.referenceRecord.genre}
+
{context.referenceRecord.ISBN}
+
+ );
+}
+```
+
+**Tip**: As with ``, you can call `` as many times as you need in the same component, react-admin will only make one call to `dataProvider.getManyReference()` per reference.
+
+## Props
+
+| Prop | Required | Type | Default | Description |
+| -------------- | -------- | ------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------- |
+| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'book_details' |
+| `target` | Required | string | - | Target field carrying the relationship on the referenced resource, e.g. 'book_id' |
+| `children` | Optional\* | `Element` | - | React component to render the referenced record. |
+| `render` | Optional\* | `(ReferenceFieldContext) => Element` | - | A function that takes the `ReferenceFieldContext` and return a React element |
+| `empty` | Optional | `ReactNode` | - | The text or element to display when the referenced record is empty |
+| `filter` | Optional | `Object` | `{}` | Used to filter referenced records |
+| `link` | Optional | `string | Function` | `edit` | Target of the link wrapping the rendered child. Set to `false` to disable the link. |
+| `queryOptions` | Optional | [`UseQueryOptions`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
+| `sort` | Optional | `{ field: String, order: 'ASC' or 'DESC' }` | `{ field: 'id', order: 'ASC' }` | Used to order referenced records |
+
+`` also accepts the [common field props](./Fields.md#common-field-props).
+
+\* Either one of children or render is required.
+
+## `children`
+
+You can pass any component of your own as children, to render the referenced record as you wish.
+You can access the list context using the `useReferenceFieldContext` hook.
+
+```jsx
+const BookShow = () => (
+
+
+
+);
+
+const BookDetails = () => {
+ const { isPending, error, referenceRecord } = useReferenceFieldContext({
+ reference,
+ target,
+ });
+
+ if (isPending) {
+ return
Loading...
;
+ }
+
+ if (error) {
+ return
{error.toString()}
;
+ }
+ if (!referenceRecord) {
+ return
No details found
;
+ }
+ return (
+
+
{referenceRecord.genre}
+
{referenceRecord.ISBN}
+
+ );
+}
+```
+
+## `render`
+
+Alternatively to children you can pass a `render` function prop to ``. The `render` function prop will receive the `ReferenceFieldContext` as its argument, allowing to inline the render logic.
+When receiving a `render` function prop the `` component will ignore the children property.
+
+```jsx
+const BookShow = () => (
+ {
+ if (isPending) {
+ return
Loading...
;
+ }
+
+ if (error) {
+ return
{error.toString()}
;
+ }
+
+ if (!referenceRecord) {
+ return
No details found
;
+ }
+ return (
+
+
{referenceRecord.genre}
+
{referenceRecord.ISBN}
+
+ );
+ }}
+ />
+);
+```
+
+## `empty`
+
+Use `empty` to customize the text displayed when the related record is empty.
+
+```jsx
+
+ ...
+
+```
+
+`empty` also accepts a translation key.
+
+```jsx
+
+ ...
+
+```
+
+`empty` also accepts a `ReactNode`.
+
+```jsx
+}
+>
+ ...
+
+```
+
+## `filter`
+
+You can also use `` in a one-to-many relationship. In that case, the first record will be displayed. The `filter` prop becomes super useful in that case, as it allows you to select the appropriate record to display.
+
+For instance, if a product has prices in many currencies, and you only want to render the price in euros, you can use:
+
+{% raw %}
+```jsx
+
+ ...
+
+```
+{% endraw %}
+
+## `link`
+
+By default, `` populates the context with a `link` value that links to the edition page of the related record. You can disable this behavior by setting the `link` prop to `false`.
+
+```jsx
+
+ ...
+
+```
+
+You can also set the `link` prop to a string, which will be used as the link type. It can be either `edit`, `show`, a route path, or a function returning a route path based on the given record.
+
+{% raw %}
+```jsx
+ `/custom/${record.id}`}
+>
+ ...
+
+```
+{% endraw %}
+
+## `queryOptions`
+
+`` uses `react-query` to fetch the related record. You can set [any of `useQuery` options](https://tanstack.com/query/v5/docs/react/reference/useQuery) via the `queryOptions` prop.
+
+For instance, if you want to disable the refetch on window focus for this query, you can use:
+
+{% raw %}
+```jsx
+
+ ...
+
+```
+{% endraw %}
+
+## `reference`
+
+The name of the resource to fetch for the related records.
+
+For instance, if you want to display the details of a given book, the `reference` name should be `book_details`:
+
+```jsx
+
+ ...
+
+```
+
+## `sort`
+
+You can also use `` in a one-to-many relationship. In that case, the first record will be displayed. This is where the `sort` prop comes in handy. It allows you to select the appropriate record to display.
+
+
+
+For instance, if you want to display the latest message in a discussion, you can use:
+
+{% raw %}
+```jsx
+
+ ...
+
+```
+{% endraw %}
+
+## `target`
+
+The name of the field carrying the relationship on the referenced resource.
+
+For example, in the following schema, the relationship is carried by the `book_id` field:
+
+```
+┌──────────────┐ ┌──────────────┐
+│ books │ │ book_details │
+│--------------│ │--------------│
+│ id │───┐ │ id │
+│ title │ └──╼│ book_id │
+│ published_at │ │ genre │
+└──────────────┘ │ ISBN │
+ └──────────────┘
+```
+
+In that case, the `target` prop should be set to `book_id`:
+
+```jsx
+
+ ...
+
+```
diff --git a/docs/ShowBase.md b/docs/ShowBase.md
index 61ecdbc58f3..7e68a762962 100644
--- a/docs/ShowBase.md
+++ b/docs/ShowBase.md
@@ -62,7 +62,8 @@ const App = () => (
| Prop | Required | Type | Default | Description
|------------------|----------|-------------------|---------|--------------------------------------------------------
-| `children` | Required | `ReactNode` | | The components rendering the record fields
+| `children` | Optional | `ReactNode` | | The components rendering the record fields
+| `render` | Optional | `(props: ShowControllerResult) => ReactNode` | | Alternative to children, a function that takes the ShowController context and renders the form
| `disable Authentication` | Optional | `boolean` | | Set to `true` to disable the authentication check
| `empty WhileLoading` | Optional | `boolean` | | Set to `true` to return `null` while the list is loading
| `id` | Optional | `string` | | The record identifier. If not provided, it will be deduced from the URL
@@ -106,6 +107,33 @@ const BookShow = () => (
```
{% endraw %}
+## `render`
+
+Alternatively, you can pass a `render` function prop instead of children. This function will receive the `ShowContext` as argument.
+
+{% raw %}
+```jsx
+import { ShowBase, TextField, DateField, ReferenceField, WithRecord } from 'react-admin';
+
+const BookShow = () => (
+ {
+ if (isPending) {
+ return