1059 lines
37 KiB
Markdown
1059 lines
37 KiB
Markdown
|
---
|
||
|
layout: default
|
||
|
title: "Field Components"
|
||
|
---
|
||
|
|
||
|
# Field Components
|
||
|
|
||
|
A `Field` component displays a given property of a REST resource. Such components are used in the `List` view, but you can also use them in the `Edit` and `Create` views for read-only fields. The most usual of all field components is `<TextField>`:
|
||
|
|
||
|
```jsx
|
||
|
// in src/posts.js
|
||
|
import React from 'react';
|
||
|
import { List, Datagrid, TextField } from 'react-admin';
|
||
|
|
||
|
export const PostList = (props) => (
|
||
|
<List {...props}>
|
||
|
<Datagrid>
|
||
|
<TextField source="id" />
|
||
|
<TextField source="title" />
|
||
|
<TextField source="body" />
|
||
|
</Datagrid>
|
||
|
</List>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
All field components accept the following attributes:
|
||
|
|
||
|
* `source`: Property name of your entity to view/edit. This attribute is required.
|
||
|
* `label`: Used as a table header of an input label. Defaults to the `source` when omitted.
|
||
|
* `sortable`: Should the list be sortable using `source` attribute? Defaults to `true`.
|
||
|
* `className`: A class name (usually generated by JSS) to customize the look and feel of the field element itself
|
||
|
* `cellClassName`: A class name (usually generated by JSS) to customize the look and feel of the field container (e.g. the `<td>` in a `Datagrid`).
|
||
|
* `headerClassName`: A class name (usually generated by JSS) to customize the look and feel of the field header (e.g. the `<th>` in a `Datagrid`).
|
||
|
* `addLabel`: Defines the visibility of the label when the field is not in a `Datagrid`. Default value is `true`.
|
||
|
* `textAlign`: Defines the text alignment inside a cell. Supports `left` (the default) and `right`.
|
||
|
|
||
|
{% raw %}
|
||
|
```jsx
|
||
|
<TextField source="zb_title" label="Title" style={{ color: 'purple' }} />
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
**Tip**: You can use field components inside the `Edit` or `Show` views, too:
|
||
|
|
||
|
```jsx
|
||
|
export const PostShow = ({ ...props }) => (
|
||
|
<Show {...props}>
|
||
|
<SimpleShowLayout>
|
||
|
<TextField source="title" />
|
||
|
</SimpleShowLayout>
|
||
|
</Show>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
**Tip**: If you display a record with a complex structure, you can use a path with dot separators as the `source` attribute. For instance, if the API returns the following 'book' record:
|
||
|
|
||
|
```js
|
||
|
{
|
||
|
id: 1234,
|
||
|
title: 'War and Peace',
|
||
|
author: {
|
||
|
firstName: 'Leo',
|
||
|
lastName: 'Tolstoi'
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Then you can display the author first name as follows:
|
||
|
|
||
|
```jsx
|
||
|
<TextField source="author.firstName" />
|
||
|
```
|
||
|
|
||
|
**Tip**: If you want to format a field according to the value, use a higher-order component to do conditional formatting, as described in the [Theming documentation](./Theming.md#conditional-formatting).
|
||
|
|
||
|
**Tip**: If your interface has to support multiple languages, don't use the `label` prop, and put the localized labels in a dictionary instead. See the [Translation documentation](./Translation.md#translating-resource-and-field-names) for details.
|
||
|
|
||
|
## `<ArrayField>`
|
||
|
|
||
|
Display a collection using `<Field>` child components.
|
||
|
|
||
|
Ideal for embedded arrays of objects, e.g. `tags` and `backlinks` in the following `post` object:
|
||
|
|
||
|
```js
|
||
|
{
|
||
|
id: 123,
|
||
|
tags: [
|
||
|
{ name: 'foo' },
|
||
|
{ name: 'bar' }
|
||
|
],
|
||
|
backlinks: [
|
||
|
{
|
||
|
date: '2012-08-10T00:00:00.000Z',
|
||
|
url: 'http://example.com/foo/bar.html',
|
||
|
},
|
||
|
{
|
||
|
date: '2012-08-14T00:00:00.000Z',
|
||
|
url: 'https://blog.johndoe.com/2012/08/12/foobar.html',
|
||
|
}
|
||
|
]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The child must be an iterator component (like `<Datagrid>` or `<SingleFieldList>`).
|
||
|
|
||
|
Here is how to display all the backlinks of the current post as a `<Datagrid>`
|
||
|
|
||
|
```jsx
|
||
|
<ArrayField source="backlinks">
|
||
|
<Datagrid>
|
||
|
<DateField source="date" />
|
||
|
<UrlField source="url" />
|
||
|
</Datagrid>
|
||
|
</ArrayField>
|
||
|
```
|
||
|
|
||
|
And here is how to display all the tags of the current post as `<Chip>` components:
|
||
|
|
||
|
```jsx
|
||
|
<ArrayField source="tags">
|
||
|
<SingleFieldList>
|
||
|
<ChipField source="name" />
|
||
|
</SingleFieldList>
|
||
|
</ArrayField>
|
||
|
```
|
||
|
|
||
|
**Tip**: If you need to render a collection in a custom way, it's often simpler to write your own component:
|
||
|
|
||
|
```jsx
|
||
|
const TagsField = ({ record }) => (
|
||
|
<ul>
|
||
|
{record.tags.map(item => (
|
||
|
<li key={item.name}>{item.name}</li>
|
||
|
))}
|
||
|
</ul>
|
||
|
)
|
||
|
TagsField.defaultProps = { addLabel: true };
|
||
|
```
|
||
|
|
||
|
## `<BooleanField>`
|
||
|
|
||
|
Displays a boolean value as a check.
|
||
|
|
||
|
```jsx
|
||
|
import { BooleanField } from 'react-admin';
|
||
|
|
||
|
<BooleanField source="commentable" />
|
||
|
```
|
||
|
|
||
|
![BooleanField](./img/boolean-field.png)
|
||
|
|
||
|
The `BooleanField` also includes an hidden text for accessibility (or to query in end to end tests). By default, it includes the translated label and the translated value, for example `Published: false`.
|
||
|
|
||
|
If you need to override it, you can use the `valueLabelTrue` and `valueLabelFalse` props which both accept a string. Those strings may be translation keys:
|
||
|
|
||
|
```jsx
|
||
|
// Simple texts
|
||
|
<BooleanField source="published" valueLabelTrue="Has been published" valueLabelFalse="Has not been published yet" />
|
||
|
|
||
|
// Translation keys
|
||
|
<BooleanField source="published" valueLabelTrue="myapp.published.true" valueLabelFalse="myapp.published.false" />
|
||
|
```
|
||
|
|
||
|
## `<ChipField>`
|
||
|
|
||
|
Displays a value inside a ["Chip"](https://material-ui.com/components/chips), which is Material UI's term for a label.
|
||
|
|
||
|
```jsx
|
||
|
import { ChipField } from 'react-admin';
|
||
|
|
||
|
<ChipField source="category" />
|
||
|
```
|
||
|
|
||
|
![ChipField](./img/chip-field.png)
|
||
|
|
||
|
This field type is especially useful for one to many relationships, e.g. to display a list of books for a given author:
|
||
|
|
||
|
```jsx
|
||
|
import { ChipField, SingleFieldList, ReferenceManyField } from 'react-admin';
|
||
|
|
||
|
<ReferenceManyField reference="books" target="author_id">
|
||
|
<SingleFieldList>
|
||
|
<ChipField source="title" />
|
||
|
</SingleFieldList>
|
||
|
</ReferenceManyField>
|
||
|
```
|
||
|
|
||
|
## `<DateField>`
|
||
|
|
||
|
Displays a date or datetime using the browser locale (thanks to `Date.toLocaleDateString()` and `Date.toLocaleString()`).
|
||
|
|
||
|
```jsx
|
||
|
import { DateField } from 'react-admin';
|
||
|
|
||
|
<DateField source="publication_date" />
|
||
|
```
|
||
|
|
||
|
This component accepts a `showTime` attribute (`false` by default) to force the display of time in addition to date. It uses `Intl.DateTimeFormat()` if available, passing the `locales` and `options` props as arguments. If Intl is not available, it ignores the `locales` and `options` props.
|
||
|
|
||
|
{% raw %}
|
||
|
```jsx
|
||
|
<DateField source="publication_date" />
|
||
|
// renders the record { id: 1234, publication_date: new Date('2017-04-23') } as
|
||
|
<span>4/23/2017</span>
|
||
|
|
||
|
<DateField source="publication_date" showTime />
|
||
|
// renders the record { id: 1234, publication_date: new Date('2017-04-23 23:05') } as
|
||
|
<span>4/23/2017, 11:05:00 PM</span>
|
||
|
|
||
|
<DateField source="publication_date" options={{ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }} />
|
||
|
// renders the record { id: 1234, publication_date: new Date('2017-04-23') } as
|
||
|
<span>Sunday, April 23, 2017</span>
|
||
|
|
||
|
<DateField source="publication_date" locales="fr-FR" />
|
||
|
// renders the record { id: 1234, publication_date: new Date('2017-04-23') } as
|
||
|
<span>23/04/2017</span>
|
||
|
|
||
|
<DateField source="publication_date" elStyle={{ color: 'red' }} />
|
||
|
// renders the record { id: 1234, publication_date: new Date('2017-04-23') } as
|
||
|
<span style="color:red;">4/23/2017</span>
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
See [Intl.DateTimeformat documentation](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/toLocaleDateString) for the `options` prop syntax.
|
||
|
|
||
|
**Tip**: If you need more formatting options than what `Intl.DateTimeformat` can provide, build your own field component leveraging a third-party library like [moment.js](http://momentjs.com/).
|
||
|
|
||
|
## `<EmailField>`
|
||
|
|
||
|
`<EmailField>` displays an email as a `<a href="mailto:" />` link.
|
||
|
|
||
|
```jsx
|
||
|
import { EmailField } from 'react-admin';
|
||
|
|
||
|
<EmailField source="personal_email" />
|
||
|
```
|
||
|
|
||
|
## `<FunctionField>`
|
||
|
|
||
|
If you need a special function to render a field, `<FunctionField>` is the perfect match. It passes the `record` to a `render` function supplied by the developer. For instance, to display the full name of a `user` record based on `first_name` and `last_name` properties:
|
||
|
|
||
|
```jsx
|
||
|
import { FunctionField } from 'react-admin'
|
||
|
|
||
|
<FunctionField label="Name" render={record => `${record.first_name} ${record.last_name}`} />
|
||
|
```
|
||
|
|
||
|
**Tip**: Technically, you can omit the `source` and `sortBy` properties for the `<FunctionField>` since you provide the render function. However, providing a `source` or a `sortBy` will allow the `Datagrid` to make the column sortable, since when a user clicks on a column, the `Datagrid` uses these properties to sort. Should you provide both, `sortBy` will override `source` for sorting the column.
|
||
|
|
||
|
## `<ImageField>`
|
||
|
|
||
|
If you need to display an image provided by your API, you can use the `<ImageField />` component:
|
||
|
|
||
|
```jsx
|
||
|
import { ImageField } from 'react-admin';
|
||
|
|
||
|
<ImageField source="url" title="title" />
|
||
|
```
|
||
|
|
||
|
This field is also generally used within an [<ImageInput />](./Inputs.md#imageinput) component to display preview.
|
||
|
|
||
|
The optional `title` prop points to the picture title property, used for both `alt` and `title` attributes. It can either be an hard-written string, or a path within your JSON object:
|
||
|
|
||
|
```jsx
|
||
|
// { picture: { url: 'cover.jpg', title: 'Larry Cover (French pun intended)' } }
|
||
|
|
||
|
// Title would be "picture.title", hence "Larry Cover (French pun intended)"
|
||
|
<ImageField source="picture.url" title="picture.title" />
|
||
|
|
||
|
// Title would be "Picture", as "Picture" is not a path in previous given object
|
||
|
<ImageField source="picture.url" title="Picture" />
|
||
|
```
|
||
|
|
||
|
If passed value is an existing path within your JSON object, then it uses the object attribute. Otherwise, it considers its value as an hard-written title.
|
||
|
|
||
|
|
||
|
If the record actually contains an array of images in its property defined by the `source` prop, the `src` prop will be needed to determine the `src` value of the images, for example:
|
||
|
|
||
|
```js
|
||
|
// This is the record
|
||
|
{
|
||
|
pictures: [
|
||
|
{ url: 'image1.jpg', desc: 'First image' },
|
||
|
{ url: 'image2.jpg', desc: 'Second image' },
|
||
|
],
|
||
|
}
|
||
|
|
||
|
<ImageField source="pictures" src="url" title="desc" />
|
||
|
```
|
||
|
|
||
|
## `<FileField>`
|
||
|
|
||
|
If you need to display a file provided by your API, you can use the `<FileField />` component:
|
||
|
|
||
|
```jsx
|
||
|
import { FileField } from 'react-admin';
|
||
|
|
||
|
<FileField source="url" title="title" />
|
||
|
```
|
||
|
|
||
|
This field is also generally used within an [<FileInput />](./Inputs.md#fileinput) component to display preview.
|
||
|
|
||
|
The optional `title` prop points to the file title property, used for `title` attributes. It can either be an hard-written string, or a path within your JSON object:
|
||
|
|
||
|
```jsx
|
||
|
// { file: { url: 'doc.pdf', title: 'Presentation' } }
|
||
|
|
||
|
// Title would be "file.title", hence "Presentation"
|
||
|
<FileField source="file.url" title="file.title" />
|
||
|
|
||
|
// Title would be "File", as "File" is not a path in previous given object
|
||
|
<FileField source="file.url" title="File" />
|
||
|
```
|
||
|
|
||
|
If passed value is an existing path within your JSON object, then it uses the object attribute. Otherwise, it considers its value as an hard-written title.
|
||
|
|
||
|
If the record actually contains an array of files in its property defined by the `source` prop, the `src` prop will be needed to determine the `href` value of the links, for example:
|
||
|
|
||
|
```js
|
||
|
// This is the record
|
||
|
{
|
||
|
files: [
|
||
|
{ url: 'image1.jpg', desc: 'First image' },
|
||
|
{ url: 'image2.jpg', desc: 'Second image' },
|
||
|
],
|
||
|
}
|
||
|
|
||
|
<FileField source="files" src="url" title="desc" />
|
||
|
```
|
||
|
|
||
|
You can optionally set the `target` prop to choose which window will the link try to open in.
|
||
|
|
||
|
```jsx
|
||
|
// Will make the file open in new window
|
||
|
<FileField source="file.url" target="_blank" />
|
||
|
```
|
||
|
|
||
|
## `<NumberField>`
|
||
|
|
||
|
Displays a number formatted according to the browser locale, right aligned.
|
||
|
|
||
|
Uses `Intl.NumberFormat()` if available, passing the `locales` and `options` props as arguments. This allows perfect display of decimals, currencies, percentage, etc.
|
||
|
|
||
|
If Intl is not available, it outputs number as is (and ignores the `locales` and `options` props).
|
||
|
|
||
|
{% raw %}
|
||
|
```jsx
|
||
|
import { NumberField } from 'react-admin';
|
||
|
|
||
|
<NumberField source="score" />
|
||
|
// renders the record { id: 1234, score: 567 } as
|
||
|
<span>567</span>
|
||
|
|
||
|
<NumberField source="score" options={{ maximumFractionDigits: 2 }}/>
|
||
|
// renders the record { id: 1234, score: 567.3567458569 } as
|
||
|
<span>567.35</span>
|
||
|
|
||
|
<NumberField source="share" options={{ style: 'percent' }} />
|
||
|
// renders the record { id: 1234, share: 0.2545 } as
|
||
|
<span>25%</span>
|
||
|
|
||
|
<NumberField source="price" options={{ style: 'currency', currency: 'USD' }} />
|
||
|
// renders the record { id: 1234, price: 25.99 } as
|
||
|
<span>$25.99</span>
|
||
|
|
||
|
<NumberField source="price" locales="fr-FR" options={{ style: 'currency', currency: 'USD' }} />
|
||
|
// renders the record { id: 1234, price: 25.99 } as
|
||
|
<span>25,99 $US</span>
|
||
|
|
||
|
<NumberField source="score" elStyle={{ color: 'red' }} />
|
||
|
// renders the record { id: 1234, score: 567 } as
|
||
|
<span style="color:red;">567</span>
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
See [Intl.Numberformat documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString) for the `options` prop syntax.
|
||
|
|
||
|
**Tip**: If you need more formatting options than what `Intl.Numberformat` can provide, build your own field component leveraging a third-party library like [numeral.js](http://numeraljs.com/).
|
||
|
|
||
|
**Tip**: When used in a `Show` view, the right alignment may look weird. Disable it by resetting the `style` attribute:
|
||
|
|
||
|
{% raw %}
|
||
|
```jsx
|
||
|
import { NumberField } from 'react-admin';
|
||
|
|
||
|
<NumberField source="score" style={{}} />
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
## `<SelectField>`
|
||
|
|
||
|
When you need to display an enumerated field, `<SelectField>` maps the value to a string.
|
||
|
|
||
|
For instance, if the `gender` field can take values "M" and "F", here is how to display it as "Male" or "Female":
|
||
|
|
||
|
```jsx
|
||
|
import { SelectField } from 'react-admin';
|
||
|
|
||
|
<SelectField source="gender" choices={[
|
||
|
{ id: 'M', name: 'Male' },
|
||
|
{ id: 'F', name: 'Female' },
|
||
|
]} />
|
||
|
```
|
||
|
|
||
|
By default, the text is built by
|
||
|
|
||
|
- finding a choice where the 'id' property equals the field value
|
||
|
- using the 'name' property an the option text
|
||
|
|
||
|
You can also customize the properties to use for the lookup value and text, thanks to the 'optionValue' and 'optionText' attributes.
|
||
|
|
||
|
```jsx
|
||
|
const choices = [
|
||
|
{ _id: 123, full_name: 'Leo Tolstoi', sex: 'M' },
|
||
|
{ _id: 456, full_name: 'Jane Austen', sex: 'F' },
|
||
|
];
|
||
|
<SelectField source="author_id" choices={choices} optionText="full_name" optionValue="_id" />
|
||
|
```
|
||
|
|
||
|
`optionText` also accepts a function, so you can shape the option text at will:
|
||
|
|
||
|
```jsx
|
||
|
const choices = [
|
||
|
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
|
||
|
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
|
||
|
];
|
||
|
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
|
||
|
<SelectField source="author_id" choices={choices} optionText={optionRenderer} />
|
||
|
```
|
||
|
|
||
|
`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there.
|
||
|
|
||
|
```jsx
|
||
|
const choices = [
|
||
|
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
|
||
|
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
|
||
|
];
|
||
|
const FullNameField = ({ record }) => <Chip>{record.first_name} {record.last_name}</Chip>;
|
||
|
<SelectField source="author_id" choices={choices} optionText={<FullNameField />}/>
|
||
|
```
|
||
|
|
||
|
The current choice is translated by default, so you can use translation identifiers as choices:
|
||
|
|
||
|
```js
|
||
|
const choices = [
|
||
|
{ id: 'M', name: 'myroot.gender.male' },
|
||
|
{ id: 'F', name: 'myroot.gender.female' },
|
||
|
];
|
||
|
```
|
||
|
|
||
|
However, in some cases (e.g. inside a `<ReferenceField>`), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`.
|
||
|
|
||
|
```jsx
|
||
|
<SelectField source="gender" choices={choices} translateChoice={false}/>
|
||
|
```
|
||
|
|
||
|
**Tip**: `<ReferenceField>` sets `translateChoice` to `false` by default.
|
||
|
|
||
|
## `<ReferenceField>`
|
||
|
|
||
|
This component fetches a single referenced record (using the `GET_MANY` REST method), and displays one field of this record. That's why a `<ReferenceField>` must always have a child `<Field>`.
|
||
|
|
||
|
For instance, here is how to fetch the `post` related to `comment` records, and display the `title` for each:
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { List, Datagrid, ReferenceField, TextField } from 'react-admin';
|
||
|
|
||
|
export const PostList = (props) => (
|
||
|
<List {...props}>
|
||
|
<Datagrid>
|
||
|
<TextField source="id" />
|
||
|
<ReferenceField label="Author" source="user_id" reference="users">
|
||
|
<TextField source="name" />
|
||
|
</ReferenceField>
|
||
|
</Datagrid>
|
||
|
</List>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
With this configuration, `<ReferenceField>` wraps the user's name in a link to the related user `<Edit>` page.
|
||
|
|
||
|
![ReferenceField](./img/reference-field.png)
|
||
|
|
||
|
`<ReferenceField>` accepts a `reference` attribute, which specifies the resource to fetch for the related record. Also, you can use any `Field` component as child.
|
||
|
|
||
|
**Note**: You **must** add a `<Resource>` for the reference resource - react-admin needs it to fetch the reference data. You *can* omit the `list` prop in this reference if you want to hide it in the sidebar menu.
|
||
|
|
||
|
```jsx
|
||
|
<Admin dataProvider={myDataProvider}>
|
||
|
<Resource name="comments" list={CommentList} />
|
||
|
<Resource name="posts" />
|
||
|
</Admin>
|
||
|
```
|
||
|
|
||
|
To change the link from the `<Edit>` page to the `<Show>` page, set the `link` prop to "show".
|
||
|
|
||
|
```jsx
|
||
|
<ReferenceField label="User" source="userId" reference="users" link="show">
|
||
|
<TextField source="name" />
|
||
|
</ReferenceField>
|
||
|
```
|
||
|
|
||
|
By default, `<ReferenceField>` is sorted by its `source`. To specify another attribute to sort by, set the `sortBy` prop to the according attribute's name.
|
||
|
|
||
|
```jsx
|
||
|
<ReferenceField label="User" source="userId" reference="users" sortBy="user.name">
|
||
|
<TextField source="name" />
|
||
|
</ReferenceField>
|
||
|
```
|
||
|
|
||
|
You can also prevent `<ReferenceField>` from adding link to children by setting `link` to `false`.
|
||
|
|
||
|
```jsx
|
||
|
// No link
|
||
|
<ReferenceField label="User" source="userId" reference="users" link={false}>
|
||
|
<TextField source="name" />
|
||
|
</ReferenceField>
|
||
|
```
|
||
|
|
||
|
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.
|
||
|
|
||
|
```jsx
|
||
|
// Custom path
|
||
|
<ReferenceField label="User" source="userId" reference="users" link={(record, reference) => `/my/path/to/${reference}/${record.id}`}>
|
||
|
<TextField source="name" />
|
||
|
</ReferenceField>
|
||
|
```
|
||
|
|
||
|
**Tip**: React-admin accumulates and deduplicates the ids of the referenced records to make *one* `GET_MANY` call for the entire list, instead of n `GET_ONE` calls. So for instance, if the API returns the following list of comments:
|
||
|
|
||
|
```js
|
||
|
[
|
||
|
{
|
||
|
id: 123,
|
||
|
body: 'Totally agree',
|
||
|
post_id: 789,
|
||
|
},
|
||
|
{
|
||
|
id: 124,
|
||
|
title: 'You are right my friend',
|
||
|
post_id: 789
|
||
|
},
|
||
|
{
|
||
|
id: 125,
|
||
|
title: 'Not sure about this one',
|
||
|
post_id: 735
|
||
|
}
|
||
|
]
|
||
|
```
|
||
|
|
||
|
Then react-admin renders the `<CommentList>` with a loader for the `<ReferenceField>`, fetches the API for the related posts in one call (`GET http://path.to.my.api/posts?ids=[789,735]`), and re-renders the list once the data arrives. This accelerates the rendering, and minimizes network load.
|
||
|
|
||
|
## `<ReferenceManyField>`
|
||
|
|
||
|
This component fetches a list of referenced records by reverse lookup of the current `record.id` in other resource (using the `GET_MANY_REFERENCE` REST method). You can specify the target field name, i.e. the field name of the current record's id in the other resource, using the required `target` field. The result is then passed to an iterator component (like `<SingleFieldList>` or `<Datagrid>`). The iterator component usually has one or more child `<Field>` components.
|
||
|
|
||
|
For instance, here is how to fetch the `comments` related to a `post` record by matching `comment.post_id` to `post.id`, and then display the `author.name` for each, in a `<ChipField>`:
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { List, Datagrid, ChipField, ReferenceManyField, SingleFieldList, TextField } from 'react-admin';
|
||
|
|
||
|
export const PostList = (props) => (
|
||
|
<List {...props}>
|
||
|
<Datagrid>
|
||
|
<TextField source="id" />
|
||
|
<TextField source="title" type="email" />
|
||
|
<ReferenceManyField label="Comments by" reference="comments" target="post_id">
|
||
|
<SingleFieldList>
|
||
|
<ChipField source="author.name" />
|
||
|
</SingleFieldList>
|
||
|
</ReferenceManyField>
|
||
|
<EditButton />
|
||
|
</Datagrid>
|
||
|
</List>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
![ReferenceManyFieldSingleFieldList](./img/reference-many-field-single-field-list.png)
|
||
|
|
||
|
`<ReferenceManyField>` accepts a `reference` attribute, which specifies the resource to fetch for the related record. It also accepts a `source` attribute which define 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 (`post.id` in the previous example).
|
||
|
|
||
|
**Note**: You **must** add a `<Resource>` for the reference resource - react-admin needs it to fetch the reference data. You *can* omit the `list` prop in this reference if you want to hide it in the sidebar menu.
|
||
|
|
||
|
You can use a `<Datagrid>` instead of a `<SingleFieldList>` - but not inside another `<Datagrid>`! This is useful if you want to display a read-only list of related records. For instance, if you want to show the `comments` related to a `post` in the post's `<Edit>` view:
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { Edit, Datagrid, SimpleForm, DateField, EditButton, ReferenceManyField, TextField, TextInput } from 'react-admin';
|
||
|
|
||
|
export const PostEdit = (props) => (
|
||
|
<Edit {...props}>
|
||
|
<SimpleForm>
|
||
|
<TextInput disabled label="Id" source="id" />
|
||
|
<TextInput source="title" />
|
||
|
<ReferenceManyField
|
||
|
label="Comments"
|
||
|
reference="comments"
|
||
|
target="post_id"
|
||
|
>
|
||
|
<Datagrid>
|
||
|
<DateField source="created_at" />
|
||
|
<TextField source="author.name" />
|
||
|
<TextField source="body" />
|
||
|
<EditButton />
|
||
|
</Datagrid>
|
||
|
</ReferenceManyField>
|
||
|
</SimpleForm>
|
||
|
</Edit>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
![ReferenceManyFieldDatagrid](./img/reference-many-field-datagrid.png)
|
||
|
|
||
|
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
|
||
|
<ReferenceManyField perPage={10} reference="comments" target="post_id">
|
||
|
...
|
||
|
</ReferenceManyField>
|
||
|
```
|
||
|
|
||
|
And if you want to allow users to paginate the list, pass a `<Pagination>` element as the `pagination` prop:
|
||
|
|
||
|
```jsx
|
||
|
import { Pagination } from 'react-admin';
|
||
|
|
||
|
<ReferenceManyField pagination={<Pagination />} reference="comments" target="post_id">
|
||
|
...
|
||
|
</ReferenceManyField>
|
||
|
```
|
||
|
|
||
|
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
|
||
|
<ReferenceManyField sort={{ field: 'created_at', order: 'DESC' }} reference="comments" target="post_id">
|
||
|
...
|
||
|
</ReferenceManyField>
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
Also, you can filter the query used to populate the possible values. Use the `filter` prop for that.
|
||
|
|
||
|
{% raw %}
|
||
|
```jsx
|
||
|
<ReferenceManyField filter={{ is_published: true }} reference="comments" target="post_id">
|
||
|
...
|
||
|
</ReferenceManyField>
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
## `<ReferenceArrayField>`
|
||
|
|
||
|
Use `<ReferenceArrayField>` to display an list of reference values based on an array of foreign keys.
|
||
|
|
||
|
For instance, if a post has many tags, a post resource may look like:
|
||
|
|
||
|
```js
|
||
|
{
|
||
|
id: 1234,
|
||
|
title: 'Lorem Ipsum',
|
||
|
tag_ids: [1, 23, 4]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Where `[1, 23, 4]` refer to ids of `tag` resources.
|
||
|
|
||
|
`<ReferenceArrayField>` can fetch the `tag` resources related to this `post` resource by matching `post.tag_ids` to `tag.id`. `<ReferenceArrayField source="tags_ids" reference="tags">` would issue an HTTP request looking like:
|
||
|
|
||
|
```
|
||
|
http://myapi.com/tags?id=[1,23,4]
|
||
|
```
|
||
|
|
||
|
**Tip**: `<ReferenceArrayField>` fetches the related resources using the `GET_MANY` REST method, so the actual HTTP request depends on your REST client.
|
||
|
|
||
|
Once it receives the related resources, `<ReferenceArrayField>` passes them to its child component using the `ids` and `data` props, so the child must be an iterator component (like `<SingleFieldList>` or `<Datagrid>`). The iterator component usually has one or more child `<Field>` components.
|
||
|
|
||
|
Here is how to fetch the list of tags for each post in a `PostList`, and display the `name` for each `tag` in a `<ChipField>`:
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { List, Datagrid, ChipField, ReferenceArrayField, SingleFieldList, TextField } from 'react-admin';
|
||
|
|
||
|
export const PostList = (props) => (
|
||
|
<List {...props}>
|
||
|
<Datagrid>
|
||
|
<TextField source="id" />
|
||
|
<TextField source="title" />
|
||
|
<ReferenceArrayField label="Tags" reference="tags" source="tag_ids">
|
||
|
<SingleFieldList>
|
||
|
<ChipField source="name" />
|
||
|
</SingleFieldList>
|
||
|
</ReferenceArrayField>
|
||
|
<EditButton />
|
||
|
</Datagrid>
|
||
|
</List>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
**Note**: You **must** add a `<Resource>` component for the reference resource to your `<Admin>` component, because react-admin needs it to fetch the reference data. You can omit the `list` prop in this Resource if you don't want to show an entry for it in the sidebar menu.
|
||
|
|
||
|
```jsx
|
||
|
export const App = () => (
|
||
|
<Admin dataProvider={restProvider('http://path.to.my.api')}>
|
||
|
<Resource name="posts" list={PostList} />
|
||
|
<Resource name="tags" /> // <= this one is compulsory
|
||
|
</Admin>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
In an Edit of Show view, you can combine `<ReferenceArrayField>` with `<Datagrid>` to display a related resources in a table. For instance, to display more details about the tags related to a post in the `PostShow` view:
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { Show, SimpleShowLayout, TextField, ReferenceArrayField, Datagrid, ShowButton } from 'react-admin';
|
||
|
|
||
|
export const PostShow = (props) => (
|
||
|
<Show {...props}>
|
||
|
<SimpleShowLayout>
|
||
|
<TextField source="id" />
|
||
|
<TextField source="title" />
|
||
|
<ReferenceArrayField label="Tags" reference="tags" source="tag_ids">
|
||
|
<Datagrid>
|
||
|
<TextField source="id" />
|
||
|
<TextField source="name" />
|
||
|
<ShowButton />
|
||
|
</Datagrid>
|
||
|
</ReferenceArrayField>
|
||
|
<EditButton />
|
||
|
</SimpleShowLayout>
|
||
|
</Show>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
## `<RichTextField>`
|
||
|
|
||
|
This component displays some HTML content. The content is "rich" (i.e. unescaped) by default.
|
||
|
|
||
|
```jsx
|
||
|
import { RichTextField } from 'react-admin';
|
||
|
|
||
|
<RichTextField source="body" />
|
||
|
```
|
||
|
|
||
|
![RichTextField](./img/rich-text-field.png)
|
||
|
|
||
|
The `stripTags` attribute (`false` by default) allows you to remove any HTML markup, preventing some display glitches (which is especially useful in list views).
|
||
|
|
||
|
```jsx
|
||
|
import { RichTextField } from 'react-admin';
|
||
|
|
||
|
<RichTextField source="body" stripTags />
|
||
|
```
|
||
|
|
||
|
## `<TextField>`
|
||
|
|
||
|
The most simple as all fields, `<TextField>` simply displays the record property as plain text.
|
||
|
|
||
|
```jsx
|
||
|
import { TextField } from 'react-admin';
|
||
|
|
||
|
<TextField label="Author Name" source="name" />
|
||
|
```
|
||
|
|
||
|
## `<UrlField>`
|
||
|
|
||
|
`<UrlField>` displays an url in an `< a href="">` tag.
|
||
|
|
||
|
```jsx
|
||
|
import { UrlField } from 'react-admin';
|
||
|
|
||
|
<UrlField source="site_url" />
|
||
|
```
|
||
|
|
||
|
## Styling Fields
|
||
|
|
||
|
All field components accept a `className` prop, allowing you to customize their style to your liking. We advise you to use the Material UI styling solution, JSS, to generate those classes. See their [documentation](https://material-ui.com/customization/css-in-js/#api) about that.
|
||
|
|
||
|
```jsx
|
||
|
import { makeStyles } from '@material-ui/core/styles';
|
||
|
|
||
|
const useStyles = makeStyles({
|
||
|
price: { color: 'purple' },
|
||
|
});
|
||
|
|
||
|
const PriceField = props => {
|
||
|
const classes = useStyles();
|
||
|
return <TextField className={classes.price} {...props} />;
|
||
|
};
|
||
|
|
||
|
export const ProductList = (props) => (
|
||
|
<List {...props}>
|
||
|
<Datagrid>
|
||
|
<PriceField source="price" />
|
||
|
</Datagrid>
|
||
|
</List>
|
||
|
);
|
||
|
|
||
|
// renders in the Datagrid as
|
||
|
<td><span class="[class name generated by JSS]">2</span></td>
|
||
|
```
|
||
|
|
||
|
React-admin usually delegates the rendering of fields components to material-ui components. Refer to the material-ui documentation to see the default styles for elements.
|
||
|
|
||
|
You may want to customize the cell style inside a `DataGrid`. You can use the `cellClassName` for that:
|
||
|
|
||
|
{% raw %}
|
||
|
```jsx
|
||
|
import { makeStyles } from '@material-ui/core/styles';
|
||
|
|
||
|
const useStyles = makeStyles({
|
||
|
priceCell: { fontWeight: 'bold' },
|
||
|
});
|
||
|
|
||
|
const PriceField = props => {
|
||
|
const classes = useStyles();
|
||
|
return <TextField cellClassName={classes.priceCell} {...props} />;
|
||
|
};
|
||
|
|
||
|
export const ProductList = (props) => (
|
||
|
<List {...props}>
|
||
|
<Datagrid>
|
||
|
<PriceField source="price" />
|
||
|
</Datagrid>
|
||
|
</List>
|
||
|
);
|
||
|
|
||
|
// renders in the Datagrid as
|
||
|
<td class="[class name generated by JSS]"><span>2</span></td>
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
You may want to override the field header (the `<th>` element in the `Datagrid`). In that case, use the `headerClassName` prop:
|
||
|
|
||
|
{% raw %}
|
||
|
```jsx
|
||
|
import { makeStyles } from '@material-ui/core/styles';
|
||
|
|
||
|
const useStyles = makeStyles({
|
||
|
priceHeader: { fontWeight: 'bold' },
|
||
|
});
|
||
|
|
||
|
const PriceField = props => {
|
||
|
const classes = useStyles();
|
||
|
return <TextField headerClassName={classes.priceHeader} {...props} />;
|
||
|
}
|
||
|
|
||
|
export const ProductList = (props) => (
|
||
|
<List {...props}>
|
||
|
<Datagrid>
|
||
|
<PriceField source="price" />
|
||
|
</Datagrid>
|
||
|
</List>
|
||
|
);
|
||
|
// renders in the table header as
|
||
|
<th class="[class name generated by JSS]"><button>Price</button></td>
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
Finally, sometimes, you just want to right align the text of a cell. Use the `textAlign` prop, which accepts either `left` or `right`:
|
||
|
|
||
|
{% raw %}
|
||
|
```jsx
|
||
|
const PriceField = props => (
|
||
|
<TextField {...props} />
|
||
|
);
|
||
|
|
||
|
PriceField.defaultProps = {
|
||
|
textAlign: 'right',
|
||
|
};
|
||
|
```
|
||
|
{% endraw %}
|
||
|
|
||
|
## Writing Your Own Field Component
|
||
|
|
||
|
If you don't find what you need in the list above, you can write your own Field component. It must be a regular React component, accepting not only a `source` attribute, but also a `record` attribute. React-admin will inject the `record` based on the API response data at render time. The field component only needs to find the `source` in the `record` and display it.
|
||
|
|
||
|
For instance, here is an equivalent of react-admin's `<TextField>` component:
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import PropTypes from 'prop-types';
|
||
|
|
||
|
const TextField = ({ source, record = {} }) => <span>{record[source]}</span>;
|
||
|
|
||
|
TextField.propTypes = {
|
||
|
label: PropTypes.string,
|
||
|
record: PropTypes.object,
|
||
|
source: PropTypes.string.isRequired,
|
||
|
};
|
||
|
|
||
|
export default TextField;
|
||
|
```
|
||
|
|
||
|
**Tip**: The `label` attribute isn't used in the `render()` method, but react-admin uses it to display the table header.
|
||
|
|
||
|
**Tip**: If you want to support deep field sources (e.g. source values like `author.name`), use [`lodash/get`](https://www.npmjs.com/package/lodash.get) to replace the simple object lookup:
|
||
|
|
||
|
```jsx
|
||
|
import get from 'lodash/get';
|
||
|
const TextField = ({ source, record = {} }) => <span>{get(record, source)}</span>;
|
||
|
```
|
||
|
|
||
|
If you are not looking for reusability, you can create even simpler components, with no attributes. Let's say an API returns user records with `firstName` and `lastName` properties, and that you want to display a full name in a user list.
|
||
|
|
||
|
```js
|
||
|
{
|
||
|
id: 123,
|
||
|
firstName: 'John',
|
||
|
lastName: 'Doe'
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The component will be:
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { List, Datagrid, TextField } from 'react-admin';
|
||
|
|
||
|
const FullNameField = ({ record = {} }) => <span>{record.firstName} {record.lastName}</span>;
|
||
|
FullNameField.defaultProps = { label: 'Name' };
|
||
|
|
||
|
export const UserList = (props) => (
|
||
|
<List {...props}>
|
||
|
<Datagrid>
|
||
|
<FullNameField source="lastName" />
|
||
|
</Datagrid>
|
||
|
</List>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
**Tip**: In such custom fields, the `source` is optional. React-admin uses it to determine which column to use for sorting when the column header is clicked. In case you use the `source` property for additional purposes, the sorting can be overridden by the `sortBy` property on any `Field` component.
|
||
|
|
||
|
## Adding Label To Custom Field Components In The Show View
|
||
|
|
||
|
React-admin lets you use the same `Field` components in the `List` view and in the `Show` view. But if you use the `<FullNameField>` custom field component defined earlier in a `Show` view, something is missing: the `Field` label. Why do other fields have a label and not this custom `Field`? And how can you create a `Field` component that has a label in the `Show` view, but not in the `List` view?
|
||
|
|
||
|
React-admin uses a trick: the `Show` view layouts (`<SimpleShowLayout>` and `<TabbedShowLayout>`) inspect their `Field` children, and whenever one has the `addLabel` prop set to `true`, the layout adds a label.
|
||
|
|
||
|
That means that the only thing you need to add to a custom component to make it usable in a `Show` view is a `addLabel: true` default prop.
|
||
|
|
||
|
```js
|
||
|
FullNameField.defaultProps = {
|
||
|
addLabel: true,
|
||
|
};
|
||
|
```
|
||
|
|
||
|
## Hiding A Field Based On The Value Of Another
|
||
|
|
||
|
In a Show view, you may want to display or hide fields based on the value of another field - for instance, show an `email` field only if the `hasEmail` boolean field is `true`.
|
||
|
|
||
|
For such cases, you can use the custom field approach: use the injected `record` prop, and render another Field based on the value.
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { EmailField } from 'react-admin';
|
||
|
|
||
|
const ConditionalEmailField = ({ record, ...rest }) =>
|
||
|
record && record.hasEmail
|
||
|
? <EmailField source="email" record={record} {...rest} />
|
||
|
: null;
|
||
|
|
||
|
export default ConditionalEmailField;
|
||
|
```
|
||
|
|
||
|
**Tip**: Always check that the `record` is defined before inspecting its properties, as react-admin displays the `Show` view *before* fetching the record from the data provider. So the first time it renders the show view for a resource, the `record` is `undefined`.
|
||
|
|
||
|
This `ConditionalEmailField` is properly hidden when `hasEmail` is `false`. But when `hasEmail` is `true`, the Show layout renders it... without label. And if you add a `addLabel` default prop, the `Show` layout will render the label regardless of the `hasEmail` value...
|
||
|
|
||
|
One solution is to add the label manually in the custom component:
|
||
|
|
||
|
```jsx
|
||
|
import React from 'react';
|
||
|
import { Labeled, EmailField } from 'react-admin';
|
||
|
|
||
|
const ConditionalEmailField = ({ record, ...rest }) =>
|
||
|
record && record.hasEmail
|
||
|
? (
|
||
|
<Labeled label="Email">
|
||
|
<EmailField source="email" record={record} {...rest} />
|
||
|
</Labeled>
|
||
|
)
|
||
|
: null;
|
||
|
|
||
|
export default ConditionalEmailField;
|
||
|
```
|
||
|
|
||
|
This comes with a drawback, though: the `<ConditionalEmailField>` cannot be used in a List view anymore, as it will always have a label. If you want to reuse the custom component in a List, this isn't the right solution.
|
||
|
|
||
|
An alternative solution is to split the `<Show>` component. Under the hood, the `<Show>` component is composed of two sub components: the `<ShowController>` component, which fetches the record, and the `<ShowView>`, which is responsible for rendering the view title, actions, and children. `<ShowController>` uses the *render props* pattern:
|
||
|
|
||
|
```jsx
|
||
|
// inside react-admin
|
||
|
const Show = props => (
|
||
|
<ShowController {...props}>
|
||
|
{controllerProps => <ShowView {...props} {...controllerProps} />}
|
||
|
</ShowController>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
The `<ShowController>` fetches the `record` from the data provider, and passes it to its child function when received (among the `controllerProps`). That means the following code:
|
||
|
|
||
|
```jsx
|
||
|
import { Show, SimpleShowLayout, TextField } from 'react-admin';
|
||
|
|
||
|
const UserShow = props => (
|
||
|
<Show {...props}>
|
||
|
<SimpleShowLayout>
|
||
|
<TextField source="username" />
|
||
|
<TextField source="email" />
|
||
|
</SimpleShowLayout>
|
||
|
</Show>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
Is equivalent to:
|
||
|
|
||
|
```jsx
|
||
|
import { ShowController, ShowView, SimpleShowLayout, TextField } from 'react-admin';
|
||
|
|
||
|
const UserShow = props => (
|
||
|
<ShowController {...props}>
|
||
|
{controllerProps =>
|
||
|
<ShowView {...props} {...controllerProps}>
|
||
|
<SimpleShowLayout>
|
||
|
<TextField source="username" />
|
||
|
<TextField source="email" />
|
||
|
</SimpleShowLayout>
|
||
|
</ShowView>
|
||
|
}
|
||
|
</ShowController>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
If you want one field to be displayed based on the `record`, for instance to display the email field only if the `hasEmail` field is `true`, you just need to test the value from `controllerProps.record`, as follows:
|
||
|
|
||
|
```jsx
|
||
|
import { ShowController, ShowView, SimpleShowLayout, TextField } from 'react-admin';
|
||
|
|
||
|
const UserShow = props => (
|
||
|
<ShowController {...props}>
|
||
|
{controllerProps =>
|
||
|
<ShowView {...props} {...controllerProps}>
|
||
|
<SimpleShowLayout>
|
||
|
<TextField source="username" />
|
||
|
{controllerProps.record && controllerProps.record.hasEmail &&
|
||
|
<TextField source="email" />
|
||
|
}
|
||
|
</SimpleShowLayout>
|
||
|
</ShowView>
|
||
|
}
|
||
|
</ShowController>
|
||
|
);
|
||
|
```
|
||
|
|
||
|
And now you can use a regular Field component, and the label displays correctly in the Show view.
|