Field Components
A Field
component displays a given property of a record. Such components are used in the List
and Show
views, but you can also use them anywhere in your application, as long as there is a RecordContext
.
Anatomy Of A Field
Field
components read the current record
from the current RecordContext
(set by react-admin). There is nothing magic there - you can easily write your own:
import { useRecordContext } from 'react-admin';
const PurpleTextField = ({ source }) => {
const record = useRecordContext();
return (<span style={{ color: 'purple' }}>{record && record[source]}</span>);
};
Tip: Every time it renders a record, react-admin creates a RecordContext
. This includes datagrid rows, simple list items, reference fields, show, and edit pages. You can even create a RecordContext
yourself and use react-admin Fields in custom pages.
React-admin Field components also accept a record
prop. This allows you to use them outside a RecordContext
, or to use another record
than the one in the current context.
// a post looks like
// { id: 123, title: "Hello, world", author: "John Doe", body: "..." }
const PostShow = ({ id }) => {
const { data, isLoading } = useGetOne('books', { id });
if (isLoading) return <span>Loading</span>;
return (
<dl>
<dt>Title</dt>
<dd><TextField record={data} source="title" /></dd>
<dt>Author</dt>
<dd><PurpleTextField record={data} source="author" /></dd>
</dl>
);
}
Usage
To render a record field (e.g. record.title
), choose the Field component that corresponds to the field type (e.g. TextField
for a text field) and pass the field name (e.g. title
) as the source
prop.
So the following Show view:
import { TextField } from 'react-admin';
export const BookShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="title" />
</SimpleShowLayout>
</Show>
);
When rendered for the following record:
{
id: 123,
title: "War And Peace",
}
Will render
<Typography component="span" variant="body2">
War And Peace
</Typography>
Field components are generally used in List and Show views, as children of <Datagrid>
, <SimpleShowLayout>
, and <TabbedShowLayout>
. The parent component usually reads their source
and/or label
prop to add a title.
// in src/posts.js
import * as React from "react";
import { Show, SimpleShowLayout, TextField, DateField, RichTextField } from 'react-admin';
export const PostShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="title" />
<TextField source="teaser" />
<RichTextField source="body" />
<DateField label="Publication date" source="published_at" />
</SimpleShowLayout>
</Show>
);
Tip: You can use field components inside the Edit
and Create
views, too, to render read-only values in a form:
export const PostEdit = () => (
<Edit>
<SimpleForm>
<TextField source="id" /> {/* read-only */}
<TextInput source="title" />
</SimpleForm>
</Edit>
);
React-admin comes with about 20 field components, specialized in rendering numbers, image URLs, booleans, arrays, etc. And if you can’t find a field for your need, you can always create your own.
Common Field Props
All Field components accept the following props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
source |
Required | string |
- | Name of the property to display |
label |
Optional | string | ReactElement |
source |
Used as a Datagrid column header or in a Show layout |
record |
Optional | Object |
- | Object containing the properties to display, to override the record from the current RecordContext |
sortable |
Optional | boolean |
true |
When used in a List , should the list be sortable using the source attribute? Setting it to false disables the click handler on the column header. |
sortBy |
Optional | string |
source |
When used in a List , specifies the actual source to be used for sorting when the user clicks the column header |
sortByOrder |
Optional | ASC | DESC |
ASC |
When used in a List , specifies the sort order to be used for sorting when the user clicks the column header |
className |
Optional | string |
- | A class name (usually generated by JSS) to customize the look and feel of the field element itself |
textAlign |
Optional | string |
‘left’ | Defines the text alignment inside a cell. Set to right for right alignment (e.g. for numbers) |
emptyText |
Optional | string |
’’ | Defines a text to be shown when a field has no value (not supported in array fields) |
sx |
Optional | SxProps |
’’ | Material UI shortcut for defining custom styles with access to the theme |
className
CSS class name passed to the root component.
<TextField source="title" className="number" />
Note: To customize field styles, prefer the sx
prop.
emptyText
By default, a Field renders an empty string when the record has no value for that field. You can override this behavior by setting the emptyText
prop. The emptyText supports i8nProvider translation, if the translation is not found it will display the value as default.
const PostList = () => (
<List>
<Datagrid>
<TextField source="title" />
<TextField source="author" emptyText="missing data" />
</Datagrid>
</List>
);
label
By default, a Field doesn’t render any label - just the value. But when rendering several fields on the same screen, it’s necessary to label them. That’s why components like <SimpleShowLayout>
and <Datagrid>
read the field source
, and use a humanized version as the label (e.g. source="title"
gives the label Title
).
You can customize this automated label by specifying a label
prop. <SimpleShowLayout>
and <Datagrid>
will then use the label
prop instead of the source
prop to label the field.
// label can be a string
<TextField source="author.name" label="Author" />
// the label is automatically translated, so you can use translation identifiers
<TextField source="author.name" label="ra.field.author" />
// you can also use a React element
<TextField source="author.name" label={<FieldTitle label="Author" />} />
Tip: If your admin has to support multiple languages, don’t use the label
prop, and put the localized labels in a dictionary instead. See the Translation documentation for details.
Tip: You can opt out of the label decoration by passing false
to the label
prop.
// No label will be added
<TextField source="author.name" label={false} />
Note: This prop has no effect when rendering a field outside a <Datagrid>
, a <SimpleShowLayout>
, a <TabbedShowLayout>
, a <SimpleForm>
, or a <TabbedForm>
.
record
By default, fields use the record
from the RecordContext
. But you can override it by passing the record
prop - e.g. if you’re rendering a field outside a RecordContext
, or if you want to use a different record than the one in the context.
<TextField source="title" record={{ id: 123, title: "Hello" }} />
sortable
In a <Datagrid>
, users can change the sort field and order by clicking on the column headers. You may want to disable this behavior for a given field (e.g. for reference or computed fields). In that case, pass the sortable
prop to <Field>
with a false
value.
const PostList = () => (
<List>
<Datagrid>
<TextField source="title" />
<ReferenceField source="author_id" sortable={false}>
<TextField source="name" />
</ReferenceField>
</Datagrid>
</List>
);
Note: This prop has no effect when rendering a field outside a <Datagrid>
.
sortBy
In a <Datagrid>
, users can change the sort field and order by clicking on the column headers. <Datagrid>
uses the Field source
to determine the sort field (e.g. clicking on the column header for the <TextField source="title" />
field sorts the list according to the title
field).
You may want to use a different sort field than the source
, e.g. for Reference fields. In that case, use the sortBy
prop to specify the sort field.
const PostList = () => (
<List>
<Datagrid>
<TextField source="title" />
<ReferenceField source="author_id" sortBy="author.name">
<TextField source="name" />
</ReferenceField>
</Datagrid>
</List>
);
Note: This prop has no effect when rendering a field outside a <Datagrid>
.
sortByOrder
By default, when users click on a <Datagrid>
column header, react-admin reorders the list using the field source, with an ascending order. For some fields, it brings unexpected results. For instance, when clicking on a “Last seen at” header, users probably expect to see the users seen more recently.
You can change the default sort field order by using the sortByOrder
prop.
const PostList = () => (
<List>
<Datagrid>
<TextField source="title" />
<DateField source="updated_at" sortByOrder="DESC" />
</Datagrid>
</List>
);
Note: This prop has no effect when rendering a field outside a <Datagrid>
.
source
The name of the property to display. Can contain dots for accessing properties of nested objects.
<TextField source="author.first_name" />
sx
Like all react-admin components, you can customize the style of Field components using the sx
prop.
import { List, Datagrid, WrapperField, TextField } from 'react-admin';
const UserList = () => (
<List>
<Datagrid>
<ImageField source="avatar" sx={{ my: -2 }}/>
<TextField source="username" sx={{ color: 'lightgrey' }} />
<TextField source="email" sx={{ textOverflow: 'ellipsis' }} />
</Datagrid>
</List>
);
In addition to the root component, the sx
prop also allows you to override the style of inner components. Refer to the documentation of each Field component to see the classes that you can override.
And see the Material UI system documentation for more information.
textAlign
This prop defines the text alignment of the field when rendered inside a <Datagrid>
cell. By default, datagrid values are left-aligned ; for numeric values, it’s often better to right-align them. Set textAlign
to right
for that.
<NumberField>
already uses textAlign="right"
. Set the default value for this prop if you create a custom numeric field.
const BasketTotal = () => {
const record = useRecordContext();
if (!record) return null;
const total = record.items.reduce((total, item) => total + item.price, 0);
return <span>{total}</span>;
}
BasketTotal.defaultProps = {
textAlign: 'right',
};
Deep Field Source
Fields use the source
as a path to read the actual value (using lodash.get()
). This means you can include dots in the source name to render a deeply nested value.
For instance, if you have a record like the following:
{
id: 123,
title: "War And Peace",
author: {
name: "Leo Tolstoy",
}
}
Then you can render the author name like this:
<TextField source="author.name" />
Setting A Field Label
React-admin Field layout components like <Datagrid>
and <SimpleShowLayout>
inspect their children and use their label
prop to set the table headers or the field labels.
So inside these components, you can provide a label
prop to override the default label.
const BookList = () => (
<List>
<Datagrid>
<TextField source="title" label="Post title" />
</Datagrid>
</List>
);
The label uses the i18n layer, so you can use a translation key, too:
const BookList = () => (
<List>
<Datagrid>
<TextField source="title" label="post.title" />
</Datagrid>
</List>
);
But as Field components don’t render the label themselves (again, this is the responsibility of the parent layout component to render the label), this doesn’t work when you use a Field inside a Form, or when the field isn’t a direct child of a layout component.
To render a field with a label in such situations, wrap the field in a <Labeled>
component:
const BookEdit = () => (
<Edit>
<SimpleForm>
<Labeled label="Post title">
<TextField source="title" />
</Labeled>
</SimpleForm>
</Edit>
);
Hiding The Field Label
React-admin Field layout components like <Datagrid>
and <SimpleShowLayout>
inspect their children and use their source
prop to set the table headers or the field labels. To opt out of this behavior, pass false
to the label
prop.
// No label will be added in SimpleShowLayout
<TextField source="author.name" label={false} />
Conditional Formatting
If you want to format a field depending on the value, create another component wrapping this field, and set the sx
prop depending on the field value:
const FormattedNumberField = ({ source }) => {
const record = useRecordContext();
return <NumberField sx={{ color: record && record[source] < 0 ? 'red' : '' }} source={source} />;
};
FormattedNumberField.defaultProps = {
textAlign: 'right',
};
Combining Two Fields
You may want to render more than one field per cell (in a <Datagrid>
) or per row (in a <SimpleShowLayout>
).
In theory, you can simply put two fields inside a React Fragment (<>
):
const BookList = () => (
<List>
<Datagrid>
<TextField source="title" />
<>
<TextField source="author_first_name" />
<TextField source="author_last_name" />
</>
</Datagrid>
</List>
);
This will render a 2-columns datagrid, one with the book title, and one with the book author.
In practice, the result lacks a column title for the second column. As <Datagrid>
looks for a source
or a label
in its children, it will not find a name for the second column.
There are two solutions. The first is to use <WrapperField>
, which supports common field props (to allow inspection by parents) and renders its children:
import { List, Datagrid, WrapperField, TextField } from 'react-admin';
const BookList = () => (
<List>
<Datagrid>
<TextField source="title" />
<WrapperField label="author" sortBy="author_last_name">
<TextField source="author_first_name" />
<TextField source="author_last_name" />
</WrapperField>
</Datagrid>
</List>
);
The second solution is to use the <FunctionField>
, which accepts a render
function:
import { List, Datagrid, WrapperField, FunctionField } from 'react-admin';
const BookList = () => (
<List>
<Datagrid>
<TextField source="title" />
<FunctionField label="author" sortBy="author.last_name" render={
record => `${record.author.first_name} ${record.author.last_name}`
} />
</Datagrid>
</List>
);
Writing Your Own Field Component
If you don’t find what you need in the list of available Fields, you can write your own Field component.
It must be a regular React component, accepting a source
attribute and retrieving the record
from the RecordContext
with the useRecordContext
hook. React-admin will set the record
in this context based on the API response data at render time. The field component only needs to find the source
in the record
and display it.
Let’s see an example for an API returning user records with firstName
and lastName
properties.
{
id: 123,
firstName: 'John',
lastName: 'Doe'
}
Here is a custom field displaying the full name:
import { useRecordContext } from 'react-admin';
export const FullNameField = (props) => {
const record = useRecordContext(props);
return record ? <span>{record.firstName} {record.lastName}</span> : null;
}
FullNameField.defaultProps = { label: 'Name' };
Tip: Always check the record
is defined before inspecting its properties, as react-admin may display 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
.
You can now use this field like any other react-admin field:
import { List, Datagrid } from 'react-admin';
import { FullNameField } from './FullNameField';
export const UserList = () => (
<List>
<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.
If you build a reusable field accepting a source
props, you will probably want to support deep field sources (e.g. source values like author.name
). Use lodash/get to replace the simple object lookup. For instance, for a Text field:
import * as React from 'react';
+import get from 'lodash/get';
import { useRecordContext } from 'react-admin';
const TextField = ({ source }) => {
const record = useRecordContext();
- return record ? <span>{record[source]}</span> : null;
+ return record ? <span>{get(record, source)}</span> : null;
}
export default TextField;
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 <WithRecord>
component, or the custom field approach: write a custom field that reads the record
from the context, and renders another Field based on the value.
import { Show, SimpleShowLayout, TextField, EmailField } from 'react-admin';
const ConditionalEmailField = () => {
const record = useRecordContext();
return record && record.hasEmail ? <EmailField source="email" /> : null;
}
const UserShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="first_name" />
<TextField source="last_name" />
<ConditionalEmailField />
</SimpleShowLayout>
</Show>
);
This <ConditionalEmailField>
is properly hidden when hasEmail
is false
. But the label for the field never renders. And if you add a label
default prop, SimpleShowLayout
layout will render the label regardless of the hasEmail
value.
How about using React conditionals in UserShow
to add the <EmailField>
field only if the record.hasEmail
is true
? Unfortunately, the useRecordContext()
hook doesn’t work in <UserShow>
(as it’s the <Show>
component’s responsibility to fetch the record and put it into a context).
const UserShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="first_name" />
<TextField source="last_name" />
{/* Where can we get the record? */}
{record.hasEmail && <EmailField source="email" />}
</SimpleShowLayout>
</Show>
);
The solution is to split the <UserShow>
component into two: one that fetches the record, and one that renders the show layout. In descendants of <Show>
, you can use the useRecordContext()
hook.
const UserShow = () => (
<Show>
<UserShowLayout />
</Show>
);
const UserShowLayout = () => {
const record = useRecordContext();
if (!record) return null;
return (
<SimpleShowLayout>
<TextField source="first_name" />
<TextField source="last_name" />
{record.hasEmail && <EmailField source="email" />}
</SimpleShowLayout>
);
};
And now you can use a regular Field component, and the label displays correctly in the Show view.
Linking To Other Records
A custom Field component might need to display a link to another record. Build the URL to the distant record using the resource name and the id, as follows:
import { useRecordContext, useGetOne } from 'react-admin';
import { Link } from 'react-router-dom';
const AuthorField = () => {
const post = useRecordContext();
const { data, isLoading } = useGetOne('users', { id: post.user_id });
const userShowPage = `/users/${post.user_id}/show`;
return isLoading ? null : <Link to={userShowPage}>{data.username}</Link>;
};
Third-Party Components
You can find components for react-admin in third-party repositories.
- OoDeLally/react-admin-clipboard-list-field: a quick and customizable copy-to-clipboard field.
- MrHertal/react-admin-json-view: JSON field and input for react-admin.
- alexgschwend/react-admin-color-picker: a color field
TypeScript
All field components accept a generic type that describes the record. This lets TypeScript validate that the source
prop targets an actual field of the record:
import * as React from "react";
import { Show, SimpleShowLayout, TextField, DateField, RichTextField } from 'react-admin';
// Note that you shouldn't extend RaRecord for this to work
type Post = {
id: number;
title: string;
teaser: string;
body: string;
published_at: string;
}
export const PostShow = () => (
<Show>
<SimpleShowLayout>
<TextField<Post> source="title" />
<TextField<Post> source="teaser" />
{/* Here TS will show an error because a teasr field does not exist */}
<TextField<Post> source="teasr" />
<RichTextField<Post> source="body" />
<DateField<Post> label="Publication date" source="published_at" />
</SimpleShowLayout>
</Show>
);
Limitation: You must not extend RaRecord
for this to work or TypeScript would not be able to infer your types properties.
Specifying the record type will also allow your IDE to provide auto-completion for both the source
and sortBy
prop. Note that the sortBy
prop also accepts any string.