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>
);

post show view

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 sourceto 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.

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. This is because RaRecord extends Record<string, any> and 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.