Skip to content

Commit cce833d

Browse files
committed
Add RecordField component
1 parent 4f9f836 commit cce833d

File tree

8 files changed

+707
-16
lines changed

8 files changed

+707
-16
lines changed

docs/RecordField.md

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
---
2+
layout: default
3+
title: "The RecordField Component"
4+
---
5+
6+
# `<RecordField>`
7+
8+
`<RecordField>` displays a label and a record property.
9+
10+
![RecordField](./img/RecordField.png)
11+
12+
## Usage
13+
14+
Use `<RecordField>` as descendent of a [`RecordContextProvider`](./useRecordContext.md#creating-a-record-context) like in record detail components (`<Show>`, `<Edit>`, `<ReferenceField>`, `<ReferenceOneField>`).
15+
16+
For instance, to render the title of a book in a show view:
17+
18+
```jsx
19+
import { Show, RecordField } from 'react-admin';
20+
import { Stack } from '@mui/material';
21+
22+
export const BookShow = () => (
23+
<Show>
24+
<Stack>
25+
<RecordField source="title" />
26+
</Stack>
27+
</Show>
28+
);
29+
```
30+
31+
`<RecordField>` renders a label based on the humanized `source` prop, or on the `label` prop if present. It also grabs the `record` from the current [`RecordContext`](./useRecordContext.md), extracts the `record[source]` property, and displays it using a [`<TextField>`](./TextField.md) by default.
32+
33+
You can override the label by passing a `label` prop:
34+
35+
```jsx
36+
<RecordField source="title" label="Book title" />
37+
```
38+
39+
The `source` prop can be a [deep source](./Fields.md#deep-field-source):
40+
41+
```jsx
42+
<RecordField label="Author name" source="author.name" />
43+
```
44+
45+
You can customize the way the value is displayed by passing a Field component in the `field` prop. For example, to display a numeric value using the browser locale, use the `NumberField`:
46+
47+
```jsx
48+
import { RecordField, NumberField } from 'react-admin';
49+
50+
<RecordField source="price" field={NumberField} />
51+
```
52+
53+
If you need to pass specific props to the field component, for example to format the value, prefer passing a field component as child. In this case, the `source` passed to the `RecordField` will only be used for the label:
54+
55+
{% raw %}
56+
```jsx
57+
import { RecordField, NumberField } from 'react-admin';
58+
59+
<RecordField source="price">
60+
<NumberField source="price" options={{ style: 'currency', currency: 'USD' }} />
61+
</RecordField>
62+
```
63+
{% endraw %}
64+
65+
If you need to aggregate multiple fields, you can use the `render` prop instead, to pass a function that receives the current record and returns a React element:
66+
67+
```jsx
68+
import { RecordField } from 'react-admin';
69+
70+
<RecordField
71+
label="Name"
72+
render={record => `${record.firstName} ${record.lastName}`}
73+
/>
74+
```
75+
76+
The `source`, `field`, `children`, and `render` props are mutually exclusive.
77+
78+
## Props
79+
80+
| Prop | Required | Type | Default | Description |
81+
| ----------- | -------- | ------------------ | ------- | -------------------------------------------------------------------------------- |
82+
| `children` | Optional | ReactNode | '' | Elements rendering the actual field. |
83+
| `field` | Optional | ReactElement | `TextField` | Field component used to render the field. Ignored if `children` or `render` are set. |
84+
| `label` | Optional | string | '' | Label to render. Can be a translation key. |
85+
| `render` | Optional | record => JSX | | Function to render the field value. Ignored if `children` is set. |
86+
| `source` | Optional | string | '' | Name of the record field to render. |
87+
| `sx` | Optional | object | {} | Styles to apply to the field. |
88+
| `TypographyProps` | Optional | object | {} | Props to pass to label wrapper |
89+
| `variant` | Optional | `'default' || 'inline'` | 'default' | When `inline`, the label is displayed inline with the field value. |
90+
91+
## `children`
92+
93+
The `children` prop is used to pass a field component that will be rendered instead of the default one. The `source` prop will only be used for the label.
94+
95+
{% raw %}
96+
```jsx
97+
import { RecordField, NumberField } from 'react-admin';
98+
99+
<RecordField source="price">
100+
<NumberField source="price" options={{ style: 'currency', currency: 'USD' }} />
101+
</RecordField>
102+
```
103+
{% endraw %}
104+
105+
This ability is often used to render a field from a reference record, using [`<ReferenceField>`](./ReferenceField.md):
106+
107+
```jsx
108+
import { RecordField, ReferenceField } from 'react-admin';
109+
110+
<RecordField label="Author">
111+
<ReferenceField source="author_id" reference="users" />
112+
</RecordField>
113+
```
114+
115+
If you just need to use a field component without any special prop, prefer the `field` prop:
116+
117+
```jsx
118+
import { RecordField, NumberField } from 'react-admin';
119+
120+
<RecordField source="price" field={NumberField} />
121+
// instead of
122+
<RecordField source="price">
123+
<NumberField source="price" />
124+
</RecordField>
125+
```
126+
127+
## `field`
128+
129+
By default, `<RecordField>` uses the [`<TextField>`](./TextField.md) component to render the field value.
130+
131+
```jsx
132+
<RecordField source="title" />
133+
// equivalent to
134+
<RecordField source="title" field={TextField} />
135+
```
136+
137+
Use the `field` prop to pass a custom field component instead:
138+
139+
```jsx
140+
import { RecordField, NumberField } from 'react-admin';
141+
142+
<RecordField source="price" field={NumberField} />
143+
```
144+
145+
If you need to pass specific props to the field component, for example to format the value, prefer passing a field component as child. In this case, the `source` passed to the `RecordField` will only be used for the label:
146+
147+
{% raw %}
148+
```jsx
149+
import { RecordField, NumberField } from 'react-admin';
150+
151+
<RecordField source="price">
152+
<NumberField source="price" options={{ style: 'currency', currency: 'USD' }} />
153+
</RecordField>
154+
```
155+
{% endraw %}
156+
157+
## `label`
158+
159+
When you use the `source` prop, the label is automatically generated from the source name using the "humanize" function. For example, the source `author.name` will be displayed as "Author name".
160+
161+
You can customize the label by passing a custom [translation](./Translation.md) for the `resources.${resourceName}.fields.${source}` key. For example, if you have a resource called `posts`, and you want to customize the label for `<RecordField source="title" />` field, you can add the following translation:
162+
163+
```json
164+
{
165+
"resources": {
166+
"posts": {
167+
"fields": {
168+
"title": "Post title"
169+
}
170+
}
171+
}
172+
}
173+
```
174+
175+
If you don't use the `source` prop, or if you don't want to use the i18N features to customize the label, you can use the `label` prop to override the default label:
176+
177+
```jsx
178+
<RecordField source="title" label="Post title" />
179+
```
180+
181+
If you pass a translation key as `label`, react-admin will use the `i18nProvider` to translate it:
182+
183+
```jsx
184+
<RecordField source="title" label="resources.posts.fields.title_custom" />
185+
```
186+
187+
Finally, you can pass `false` to the `label` prop to hide the label:
188+
189+
```jsx
190+
<RecordField source="title" label={false} />
191+
```
192+
193+
Note that using `label={false}` is equivalent to rendering a `<TextField>` directly.
194+
195+
## `render`
196+
197+
The `render` prop is used to pass a function that receives the current record and returns a React element. This is useful when you need to aggregate multiple fields, or when you need to use a component that doesn't accept the `source` prop.
198+
199+
```jsx
200+
import { RecordField } from 'react-admin';
201+
202+
<RecordField
203+
label="Name"
204+
render={record => `${record.firstName} ${record.lastName}`}
205+
/>
206+
```
207+
208+
If you pass both `source` and `render`, the `source` will be used for the label only.
209+
210+
## `sx`
211+
212+
Use the `sx` prop to pass custom styles to the field.
213+
214+
{% raw %}
215+
```jsx
216+
<RecordField source="id" sx={{ opacity: 0.5 }} />
217+
```
218+
{% endraw %}
219+
220+
If you want to style the label, use the `TypographyProps` prop instead:
221+
222+
{% raw %}
223+
```jsx
224+
<RecordField
225+
source="id"
226+
TypographyProps={{ sx: { color: 'red' } }}
227+
/>
228+
```
229+
{% endraw %}
230+
231+
If you want to style the value only, prefer passing a custom component as child:
232+
233+
{% raw %}
234+
```jsx
235+
<RecordField source="id">
236+
<TextField source="id" sx={{ color: 'red' }} />
237+
</RecordField>
238+
```
239+
{% endraw %}
240+
241+
## `source`
242+
243+
Use the `source` prop to specify the name of the record field to render.
244+
245+
For example, if the current record is:
246+
247+
```json
248+
{
249+
"id": 123,
250+
"title": "My post",
251+
"author": {
252+
"name": "John Doe"
253+
}
254+
}
255+
```
256+
257+
To display the `title` field, use:
258+
259+
```jsx
260+
<RecordField source="title" />
261+
```
262+
263+
The `source` prop can be a deep source, for example `author.name`.
264+
265+
```jsx
266+
<RecordField source="author.name" />
267+
```
268+
269+
If you use the `render` or `children` prop, the `source` will only be used for the label.
270+
271+
## `TypographyProps`
272+
273+
The `TypographyProps` prop is used to pass props to the label wrapper. This is useful when you want to style the label differently from the field value.
274+
275+
{% raw %}
276+
```jsx
277+
<RecordField
278+
source="id"
279+
TypographyProps={{ sx: { color: 'red' } }}
280+
/>
281+
```
282+
{% endraw %}
283+
284+
## `variant`
285+
286+
By default, `<RecordField>` renders the label above the field value. You can use the `variant` prop to render the label inline with the field value:
287+
288+
```jsx
289+
<RecordField
290+
source="title"
291+
variant="inline"
292+
/>
293+
```
294+
295+
If you need to customize the width of the label, you can use the `TypographyProps` prop:
296+
297+
{% raw %}
298+
```jsx
299+
<RecordField
300+
source="title"
301+
variant="inline"
302+
TypographyProps={{ sx: { width: 200 } }}
303+
/>
304+
```
305+
{% endraw %}
306+
307+
But since you generally need to do it for several fields, it's preferable to do it in the parent component:
308+
309+
{% raw %}
310+
```jsx
311+
<Stack sx={{ '& .RaRecordField-label': { width: 200 } }}>
312+
<RecordField variant="inline" source="id" />
313+
<RecordField variant="inline" source="title" />
314+
<RecordField variant="inline" source="author" />
315+
<RecordField variant="inline" source="summary" />
316+
<RecordField variant="inline" source="year" field={NumberField} />
317+
</Stack>
318+
```
319+
{% endraw %}
320+
321+
## TypeScript
322+
323+
`<RecordField>` is a generic component. You can pass a type parameter to get hints for the `source` prop and type safety for the `record` argument of the `render` function.
324+
325+
```tsx
326+
import { Show, RecordField } from 'react-admin';
327+
import { Stack } from '@mui/material';
328+
329+
import { Book } from './types';
330+
331+
const BookShow = () => {
332+
const BookField = RecordField<Book>;
333+
return (
334+
<Show>
335+
<Stack>
336+
<BookField source="title" />
337+
<BookField source="author.name" />
338+
<BookField source="price" render={record => `${record.price} USD`} />
339+
</Stack>
340+
</Show>
341+
);
342+
};
343+
```

docs/Reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ title: "Index"
149149
**- R -**
150150

151151
* [`<RadioButtonGroupInput>`](./RadioButtonGroupInput.md)
152+
* [`<RecordField>`](./RecordField.md)
152153
* [`<RecordRepresentation>`](./RecordRepresentation.md)
153154
* [`<ReferenceArrayField>`](./ReferenceArrayField.md)
154155
* [`<ReferenceArrayInput>`](./ReferenceArrayInput.md)

docs/_includes/navigation.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
<li {% if page.path == 'ImageField.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./ImageField.html"><code>&lt;ImageField&gt;</code></a></li>
184184
<li {% if page.path == 'MarkdownField.md' %} class="active" {% endif %}><a class="nav-link" href="./MarkdownField.html"><code>&lt;MarkdownField&gt;</code><img class="premium" src="./img/premium.svg" /></a></li>
185185
<li {% if page.path == 'NumberField.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./NumberField.html"><code>&lt;NumberField&gt;</code></a></li>
186+
<li {% if page.path == 'RecordField.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./RecordField.html"><code>&lt;RecordField&gt;</code></a></li>
186187
<li {% if page.path == 'ReferenceField.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./ReferenceField.html"><code>&lt;ReferenceField&gt;</code></a></li>
187188
<li {% if page.path == 'ReferenceArrayField.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./ReferenceArrayField.html"><code>&lt;ReferenceArrayField&gt;</code></a></li>
188189
<li {% if page.path == 'ReferenceManyField.md' %} class="active beginner" {% else %} class="beginner" {% endif %}><a class="nav-link" href="./ReferenceManyField.html"><code>&lt;ReferenceManyField&gt;</code></a></li>

docs/img/RecordField.png

40.5 KB
Loading

examples/simple/src/comments/CommentShow.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
import * as React from 'react';
2-
import {
3-
DateField,
4-
ReferenceField,
5-
Show,
6-
SimpleShowLayout,
7-
TextField,
8-
} from 'react-admin';
2+
import { DateField, ReferenceField, RecordField, Show } from 'react-admin';
3+
import { Stack } from '@mui/material';
94

105
const CommentShow = () => (
116
<Show queryOptions={{ meta: { prefetch: ['post'] } }}>
12-
<SimpleShowLayout>
13-
<TextField source="id" />
14-
<ReferenceField source="post_id" reference="posts">
15-
<TextField source="title" />
16-
</ReferenceField>
17-
<TextField source="author.name" />
18-
<DateField source="created_at" />
19-
<TextField source="body" />
20-
</SimpleShowLayout>
7+
<Stack gap={1} sx={{ py: 1, px: 2 }}>
8+
<RecordField source="id" />
9+
<RecordField source="post_id">
10+
<ReferenceField source="post_id" reference="posts" />
11+
</RecordField>
12+
<RecordField source="author.name" />
13+
<RecordField field={DateField} source="created_at" />
14+
<RecordField source="body" />
15+
</Stack>
2116
</Show>
2217
);
2318

0 commit comments

Comments
 (0)