Skip to content

Commit 58b7a55

Browse files
committed
add doc on ReferenceManyFieldBase
1 parent 70b260c commit 58b7a55

File tree

4 files changed

+311
-28
lines changed

4 files changed

+311
-28
lines changed

docs/ReferenceFieldBase.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ storybook_path: ra-core-controller-field-referencefieldbase--basic
77
# `<ReferenceFieldBase>`
88

99
`<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)
10+
`<ReferenceFieldBase>` is a headless component, handling only the logic. This Allows to use any UI library for the render. For a version incorporating UI see [`<ReferenceField>`](/ReferenceField.html)
1111

1212
## Usage
1313

@@ -37,7 +37,7 @@ export const PostShow = () => (
3737
<TextField source="id" />
3838
<TextField source="title" />
3939
<DateField source="published_at" />
40-
<ReferenceFieldBase source="user_id" reference="users" label="Author">
40+
<ReferenceFieldBase source="user_id" reference="users" >
4141
<CustomUIRenderer />
4242
</ReferenceFieldBase>
4343
</SimpleShowLayout>

docs/ReferenceManyFieldBase.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ export const PostList = () => (
126126
- [`<SimpleList>`](./SimpleList.md)
127127
- [`<EditableDatagrid>`](./EditableDatagrid.md)
128128
- [`<Calendar>`](./Calendar.md)
129-
- Or a component of your own (check the [`<WithListContext>`](./WithListContext.md) and the [`useListContext`](./useListContext.md) chapters to learn how).
130129
131130
For instance, use a `<DataTable>` to render the related records in a table:
132131

docs/ReferenceOneFieldBase.md

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
---
2+
layout: default
3+
title: "The ReferenceOneFieldBase Component"
4+
storybook_path: ra-ui-materialui-fields-referenceonefieldbase--basic
5+
---
6+
7+
# `<ReferenceOneFieldBase>`
8+
9+
This field fetches a one-to-one relationship, e.g. the details of a book, when using a foreign key on the distant resource.
10+
11+
```
12+
┌──────────────┐ ┌──────────────┐
13+
│ books │ │ book_details │
14+
│--------------│ │--------------│
15+
│ id │───┐ │ id │
16+
│ title │ └──╼│ book_id │
17+
│ published_at │ │ genre │
18+
└──────────────┘ │ ISBN │
19+
└──────────────┘
20+
```
21+
22+
`<ReferenceOneFieldBase>` behaves like `<ReferenceManyFieldBase>`: 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.
23+
24+
`<ReferenceOneFieldBase>` is a headless component, handling only the logic. This Allows to use any UI library for the render. For a version incorporating UI see [`<ReferenceOneField>`](/ReferenceOneField.html)
25+
26+
For the inverse relationships (the book linked to a book_detail), you can use a [`<ReferenceFieldBase>`](./ReferenceFieldBase.md).
27+
28+
## Usage
29+
30+
Here is how to render a field of the `book_details` resource inside a Show view for the `books` resource:
31+
32+
```jsx
33+
34+
const BookShow = () => (
35+
<Show>
36+
<SimpleShowLayout>
37+
<TextField source="title" />
38+
<DateField source="published_at" />
39+
<ReferenceField source="authorId" reference="authors" />
40+
<ReferenceOneFieldBase reference="book_details" target="book_id">
41+
<MyBookView />
42+
</ReferenceOneFieldBase>
43+
</SimpleShowLayout>
44+
</Show>
45+
);
46+
47+
// with MyBookView something like
48+
const MyBookView = ({ source }) => {
49+
const context = useReferenceOneFieldContext({
50+
reference,
51+
target,
52+
});
53+
54+
if (context.isPending) {
55+
return <p>Loading...</p>;
56+
}
57+
58+
if (context.error) {
59+
return <p className="error" >{context.error.toString()}</p>;
60+
}
61+
return (
62+
<div>
63+
<p>{record ? record.genre : ''}</p>
64+
<p>{record ? record.ISBN : ''}</p>
65+
</div>
66+
);
67+
}
68+
```
69+
70+
**Tip**: As with `<ReferenceFieldBase>`, you can call `<ReferenceOneFieldBase>` as many times as you need in the same component, react-admin will only make one call to `dataProvider.getManyReference()` per reference.
71+
72+
## Props
73+
74+
| Prop | Required | Type | Default | Description |
75+
| -------------- | -------- | ------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------- |
76+
| `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'book_details' |
77+
| `target` | Required | string | - | Target field carrying the relationship on the referenced resource, e.g. 'book_id' |
78+
| `children` | Required if no render | `Element` | - | React component to render the referenced record, the component need to use useReferenceOneFieldContext to access the context. |
79+
| `render` | Required if no children | `Element` | - | A function that takes the reference field context and return a React element |
80+
| `empty` | Optional | `ReactNode` | - | The text or element to display when the referenced record is empty |
81+
| `filter` | Optional | `Object` | `{}` | Used to filter referenced records |
82+
| `link` | Optional | `string | Function` | `edit` | Target of the link wrapping the rendered child. Set to `false` to disable the link. |
83+
| `queryOptions` | Optional | [`UseQueryOptions`](https://tanstack.com/query/v5/docs/react/reference/useQuery) | `{}` | `react-query` client options |
84+
| `sort` | Optional | `{ field: String, order: 'ASC' or 'DESC' }` | `{ field: 'id', order: 'ASC' }` | Used to order referenced records |
85+
86+
`<ReferenceOneFieldBase>` also accepts the [common field props](./Fields.md#common-field-props).
87+
88+
## `children`
89+
90+
You can pass any component of your own as children, to render the referenced record as you wish.
91+
You can access the list context using the `useReferenceOneFieldController` hook.
92+
93+
```jsx
94+
const MyBookView = () => {
95+
const context = useReferenceOneFieldContext({
96+
reference,
97+
target,
98+
});
99+
100+
if (context.isPending) {
101+
return <p>Loading...</p>;
102+
}
103+
104+
if (context.error) {
105+
return <p className="error" >{context.error.toString()}</p>;
106+
}
107+
return (
108+
<div>
109+
<p>{record ? record.genre : ''}</p>
110+
<p>{record ? record.ISBN : ''}</p>
111+
</div>
112+
);
113+
}
114+
115+
const BookShow = () => (
116+
<ReferenceOneFieldBase reference="book_details" target="book_id">
117+
<MyBookView />
118+
</ReferenceOneFieldBase>
119+
);
120+
```
121+
122+
## `render`
123+
124+
Alternatively to children you can pass a render prop to `<ReferenceOneFieldBase>`. The render prop will receive the reference on field context as its argument, allowing to inline the render logic.
125+
When receiving a render prop the `<ReferenceOneFieldBase>` component will ignore the children property.
126+
127+
```jsx
128+
const BookShow = () => (
129+
<ReferenceOneFieldBase
130+
reference="book_details"
131+
target="book_id"
132+
render={(context) => {
133+
if (context.isPending) {
134+
return <p>Loading...</p>;
135+
}
136+
137+
if (context.error) {
138+
return <p className="error" >{context.error.toString()}</p>;
139+
}
140+
return (
141+
<div>
142+
<p>{record ? record.genre : ''}</p>
143+
<p>{record ? record.ISBN : ''}</p>
144+
</div>
145+
);
146+
}}
147+
/>
148+
);
149+
```
150+
151+
## `empty`
152+
153+
Use `empty` to customize the text displayed when the related record is empty.
154+
155+
```jsx
156+
<ReferenceOneFieldBase label="Details" reference="book_details" target="book_id" empty="no detail">
157+
<TextField source="genre" /> (<TextField source="ISBN" />)
158+
</ReferenceOneFieldBase>
159+
```
160+
161+
`empty` also accepts a translation key.
162+
163+
```jsx
164+
<ReferenceOneFieldBase label="Details" reference="book_details" target="book_id" empty="resources.books.not_found">
165+
<TextField source="genre" /> (<TextField source="ISBN" />)
166+
</ReferenceOneFieldBase>
167+
```
168+
169+
`empty` also accepts a `ReactNode`.
170+
171+
```jsx
172+
<ReferenceOneFieldBase
173+
label="Details"
174+
reference="book_details"
175+
target="book_id"
176+
empty={<CreateButton to="/book_details/create" />}
177+
>
178+
<TextField source="genre" /> (<TextField source="ISBN" />)
179+
</ReferenceOneFieldBase>
180+
```
181+
182+
## `filter`
183+
184+
You can also use `<ReferenceOneFieldBase>` 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.
185+
186+
For instance, if a product has prices in many currencies, and you only want to render the price in euros, you can use:
187+
188+
{% raw %}
189+
```jsx
190+
<ReferenceOneFieldBase
191+
reference="product_prices"
192+
target="product_id"
193+
filter={{ currency: "EUR" }}
194+
>
195+
<NumberField source="price" />
196+
</ReferenceOneFieldBase>
197+
```
198+
{% endraw %}
199+
200+
## `link`
201+
202+
By default, `<ReferenceOneFieldBase>` will set pass a links to the edition page of the related record in the context.link. You can disable this behavior by setting the `link` prop to `false`.
203+
204+
```jsx
205+
<ReferenceOneFieldBase label="Genre" reference="book_details" target="book_id" link={false}>
206+
<TextField source="genre" />
207+
</ReferenceOneFieldBase>
208+
```
209+
210+
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.
211+
212+
{% raw %}
213+
```jsx
214+
<ReferenceOneFieldBase
215+
reference="book_details"
216+
target="book_id"
217+
link={record => `/custom/${record.id}`}
218+
>
219+
<TextField source="genre" />
220+
</ReferenceOneFieldBase>
221+
```
222+
{% endraw %}
223+
224+
## `queryOptions`
225+
226+
`<ReferenceOneFieldBase>` 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.
227+
228+
For instance, if you want to disable the refetch on window focus for this query, you can use:
229+
230+
{% raw %}
231+
```jsx
232+
<ReferenceOneFieldBase
233+
label="Genre"
234+
reference="book_details"
235+
target="book_id"
236+
queryOptions={{ refetchOnWindowFocus: false }}
237+
>
238+
<TextField source="genre" />
239+
</ReferenceOneFieldBase>
240+
```
241+
{% endraw %}
242+
243+
## `reference`
244+
245+
The name of the resource to fetch for the related records.
246+
247+
For instance, if you want to display the details of a given book, the `reference` name should be `book_details`:
248+
249+
```jsx
250+
<ReferenceOneFieldBase label="Genre" reference="book_details" target="book_id">
251+
<TextField source="genre" />
252+
</ReferenceOneFieldBase>
253+
```
254+
255+
## `sort`
256+
257+
You can also use `<ReferenceOneFieldBase>` 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.
258+
259+
![ReferenceOneFieldBase for one-to-many relationships](./img/reference-one-field-many.png)
260+
261+
For instance, if you want to display the latest message in a discussion, you can use:
262+
263+
{% raw %}
264+
```jsx
265+
<ReferenceOneFieldBase
266+
reference="messages"
267+
target="discussion_id"
268+
sort={{ field: "createdAt", order: "DESC" }}
269+
>
270+
<TextField source="body" />
271+
</ReferenceOneFieldBase>
272+
```
273+
{% endraw %}
274+
275+
## `target`
276+
277+
The name of the field carrying the relationship on the referenced resource.
278+
279+
For example, in the following schema, the relationship is carried by the `book_id` field:
280+
281+
```
282+
┌──────────────┐ ┌──────────────┐
283+
│ books │ │ book_details │
284+
│--------------│ │--------------│
285+
│ id │───┐ │ id │
286+
│ title │ └──╼│ book_id │
287+
│ published_at │ │ genre │
288+
└──────────────┘ │ ISBN │
289+
└──────────────┘
290+
```
291+
292+
In that case, the `target` prop should be set to `book_id`:
293+
294+
```jsx
295+
<ReferenceOneFieldBase label="Genre" reference="book_details" target="book_id">
296+
<TextField source="genre" />
297+
</ReferenceOneFieldBase>
298+
```
299+

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

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ResourceContextProvider,
88
TestMemoryRouter,
99
useRecordContext,
10-
useReferenceOneFieldController,
10+
useReferenceFieldContext,
1111
} from '../..';
1212

1313
export default { title: 'ra-ui-materialui/fields/ReferenceOneFieldBase' };
@@ -37,9 +37,7 @@ const Wrapper = ({ children, dataProvider = defaultDataProvider }) => (
3737
export const Basic = () => (
3838
<Wrapper>
3939
<ReferenceOneFieldBase reference="book_details" target="book_id">
40-
<MyReferenceOneField reference="book_details" target="book_id">
41-
<TextField source="ISBN" />
42-
</MyReferenceOneField>
40+
<MyReferenceOneField />
4341
</ReferenceOneFieldBase>
4442
</Wrapper>
4543
);
@@ -51,9 +49,7 @@ const dataProviderWithLoading = {
5149
export const Loading = () => (
5250
<Wrapper dataProvider={dataProviderWithLoading}>
5351
<ReferenceOneFieldBase reference="book_details" target="book_id">
54-
<MyReferenceOneField reference="book_details" target="book_id">
55-
<TextField source="ISBN" />
56-
</MyReferenceOneField>
52+
<MyReferenceOneField />
5753
</ReferenceOneFieldBase>
5854
</Wrapper>
5955
);
@@ -89,19 +85,8 @@ export const WithRenderProp = ({
8985
);
9086
};
9187

92-
const MyReferenceOneField = ({
93-
reference,
94-
target,
95-
children,
96-
}: {
97-
children: React.ReactNode;
98-
reference: string;
99-
target: string;
100-
}) => {
101-
const context = useReferenceOneFieldController({
102-
reference,
103-
target,
104-
});
88+
const MyReferenceOneField = () => {
89+
const context = useReferenceFieldContext();
10590

10691
if (context.isPending) {
10792
return <p>Loading...</p>;
@@ -110,10 +95,10 @@ const MyReferenceOneField = ({
11095
if (context.error) {
11196
return <p style={{ color: 'red' }}>{context.error.toString()}</p>;
11297
}
113-
return children;
114-
};
11598

116-
const TextField = ({ source }) => {
117-
const record = useRecordContext();
118-
return <span>{record ? record[source] : ''}</span>;
99+
return (
100+
<span>
101+
{context.referenceRecord ? context.referenceRecord.ISBN : ''}
102+
</span>
103+
);
119104
};

0 commit comments

Comments
 (0)