Fields For Relationships

React-admin provides numerous components, called β€˜Reference’ components, to deal with relationships between records. In fact, react-admin and the dataProvider interface are actually designed to facilitate the implementation of relational features such as:

  • showing the comments related to a post
  • showing the author of a post
  • choosing the author of a post
  • adding tags to a post

React-admin handles relationships regardless of the capacity of the API to manage relationships. As long as you can provide a dataProvider for your API, all the relational features will work.

React-admin provides helpers to fetch related records, depending on the type of relationship, and how the API implements it.

One-To-Many

When one record has many related records, this is called a one-to-many relationship. For instance, if an author has written several books, authors has a one-to-many relationship with books.

To fetch the books of an author with react-admin, you can use:

  • <ReferenceManyField> when the API uses a foreign key (e.g. each book has an author_id field)
  • <ReferenceArrayField> when the API uses an array of foreign keys (e.g. each author has a book_ids field)
  • <ArrayField> when the API embeds an array of records (e.g. each author has a books field)

Many-To-One

On the other hand, many-to-one relationships are the opposite of one-to-many relationships (e.g. each book has one author). To fetch the author of a book, you can use:

  • <ReferenceField> when the API uses a foreign key (e.g. each book has an author_id field)
  • Deep Field Source, when the API embeds the related record (e.g. each book has an author field containing an object)

Other kinds of relationships often reduce to one-to-many relationships.

One-To-One

For instance, one-to-one relationships (e.g. a book has one book_detail) are a special type of one-to-many relationship with a cardinality of 1. To fetch the details of a book, you can use:

  • <ReferenceOneField> when the API uses a foreign key (e.g. each book_detail has a book_id field)
  • <ReferenceField> when the API uses a reverse foreign key (e.g. each book has a book_detail_id field)
  • Deep Field Source, when the API embeds the related record (e.g. each book has a book_detail field containing an object)

Many-To-Many

Also, many-to-many relationships are often modeled as two successive one-to-many relationships. For instance, if a book is co-authored by several people, we can model this as a one-to-many relationship between the book and the book_authors, and a one-to-many relationship between the book_authors and the authors. To fetch the books of an author, use:

  • <ReferenceManyToManyField> when the API uses a join table (e.g. a book_authors table with both book_id and author_id fields)
  • <ReferenceArrayField> when the API uses an array of foreign keys (e.g. each author has a book_ids field, and each book has an author_ids field)
  • <ArrayField>, when the API embeds an array of records (e.g. each author has a books field, and each book has an authors field)

Deep Field Source

When a many-to-one relationship (e.g. the author of a book) is materialized by an embedded object, then you don’t need a Reference field - you can just use any regular field, and use a compound field name (e.g. β€œauthor.first_name”).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ books            β”‚
β”‚------------------β”‚
β”‚ id               β”‚
β”‚ author           β”‚
β”‚  β”” first_name    β”‚
β”‚  β”” last_name     β”‚
β”‚  β”” date_of_birth β”‚
β”‚ title            β”‚
β”‚ published_at     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Here is an example usage:

const BookShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="title" />
            <DateField source="published_at" />
            <FunctionField 
                label="Author"
                render={record => `${record.author.first_name} ${record.author.last_name}`}
            />
            <DateField label="Author DOB" source="author.date_of_birth" />
        </SimpleShowLayout>
    </Show>
);

<ArrayField>

This field fetches a one-to-many relationship, e.g. the books of an author, when using an array embedded objects.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ author                    β”‚
β”‚---------------------------β”‚
β”‚ id                        β”‚
β”‚ first_name                β”‚
β”‚ last_name                 β”‚
β”‚ date_of_birth             β”‚
β”‚ books                     β”‚
β”‚  β”” { title, published_at} β”‚
β”‚  β”” { title, published_at} β”‚
β”‚  β”” { title, published_at} β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Here is an example usage:

const AuthorShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="first_name" />
            <TextField source="last_name" />
            <DateField source="date_of_birth" />
            <ArrayField source="books">
                <Datagrid>
                    <TextField source="title" />
                    <DateField source="published_at" />
                </Datagrid>
            </ArrayField>
        </SimpleShowLayout>
    </Show>
);

<ArrayField> creates a ListContext with the embedded records, so you can use any component relying on this context (<Datagrid>, <SimpleList>, etc.).

<ReferenceField>

This field fetches a many-to-one relationship, e.g. the author of a book, when using a foreign key.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ books        β”‚       β”‚ authors        β”‚
β”‚--------------β”‚       β”‚----------------β”‚
β”‚ id           β”‚   β”Œβ”€β”€β”€β”‚ id             β”‚
β”‚ author_id    β”‚β•Ύβ”€β”€β”˜   β”‚ first_name     β”‚
β”‚ title        β”‚       β”‚ last_name      β”‚
β”‚ published_at β”‚       β”‚ date_of_birth  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Here is an example usage:

const BookShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="title" />
            <DateField source="published_at" />
            <ReferenceField label="Author" source="author_id" reference="authors">
                <FunctionField render={record => record && `${record.first_name} ${record.last_name}`} />
            </ReferenceField>
            <ReferenceField label="Author DOB" source="author_id" reference="authors">
                <DateField source="date_of_birth" />
            </ReferenceField>
        </SimpleShowLayout>
    </Show>
);

<ReferenceField> uses the current record (a book in this example) to read the id of the reference using the foreign key (author_id). Then, it uses dataProvider.getOne('authors', { id }) fetch the related author.

<ReferenceField> creates a RecordContext with the reference record, so you can use any component relying on this context (<TextField>, <SimpleShowLayout>, etc.).

Tip: You don’t need to worry about the fact that these components calls <ReferenceField> twice on the same table. React-admin will only make one call to the API.

This is fine, but what if you need to display the author details for a list of books?

const BookList = () => (
    <List>
        <Datagrid>
            <TextField source="title" />
            <DateField source="published_at" />
            <ReferenceField label="Author" source="author_id" reference="authors">
                <FunctionField render={record => `${record.first_name} ${record.last_name}`} />
            </ReferenceField>
            <ReferenceField label="Author DOB" source="author_id" reference="authors">
                <DateField source="date_of_birth" />
            </ReferenceField>
        </Datagrid>
    </List>
);

If each row of the book list triggers one call to dataProvider.getOne('authors', { id }), and if the list counts many rows (say, 25), the app will be very slow - and possibly blocked by the API for abusive usage. This is another version of the dreaded β€œn+1 problem”.

Fortunately, <ReferenceField> aggregates and deduplicates all the renders made in a page, and creates an optimised request. In the example above, instead of n calls to dataProvider.getOne('authors', { id }), the book list will make one call to dataProvider.getMany('authors', { ids }).

<ReferenceManyField>

This field fetches a one-to-many relationship, e.g. the books of an author, when using a foreign key.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ authors        β”‚       β”‚ books        β”‚
β”‚----------------β”‚       β”‚--------------β”‚
β”‚ id             │───┐   β”‚ id           β”‚
β”‚ first_name     β”‚   └──╼│ author_id    β”‚
β”‚ last_name      β”‚       β”‚ title        β”‚
β”‚ date_of_birth  β”‚       β”‚ published_at β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Here is an example usage:

const AuthorShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="first_name" />
            <TextField source="last_name" />
            <DateField source="date_of_birth" />
            <ReferenceManyField reference="books" target="author_id">
                <Datagrid>
                    <TextField source="title" />
                    <DateField source="published_at" />
                </Datagrid>
            </ReferenceManyField>
        </SimpleShowLayout>
    </Show>
);

<ReferenceManyField> uses the current record (an author in this example) to build a filter for the list of books on the foreign key field (author_id). Then, it uses dataProvider.getManyReference('books', { target: 'author_id', id: book.id }) fetch the related books.

<ReferenceManyField> creates a ListContext with the related records, so you can use any component relying on this context (<Datagrid>, <SimpleList>, etc.).

Tip: For many APIs, there is no difference between dataProvider.getList() and dataProvider.getManyReference(). The latter is a specialized version of the former, with a predefined filter. But some APIs expose related records as a sub-route, and therefore need a special method to fetch them. For instance, the books of an author can be exposed via the following endpoint:

GET /authors/:id/books

That’s why <ReferenceManyField> uses the getManyReference() method instead of getList().

<ReferenceArrayField>

This field fetches a one-to-many relationship, e.g. the books of an author, when using an array of foreign keys.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ authors        β”‚       β”‚ books        β”‚
β”‚----------------β”‚       β”‚--------------β”‚
β”‚ id             β”‚   β”Œβ”€β”€β”€β”‚ id           β”‚
β”‚ first_name     β”‚   β”‚   β”‚ title        β”‚
β”‚ last_name      β”‚   β”‚   β”‚ published_at β”‚
β”‚ date_of_birth  β”‚   β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ book_ids       β”‚β•Ύβ”€β”€β”˜   
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       

Here is an example usage:

const AuthorShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="first_name" />
            <TextField source="last_name" />
            <DateField source="date_of_birth" />
            <ReferenceArrayField reference="books" source="book_ids">
                <Datagrid>
                    <TextField source="title" />
                    <DateField source="published_at" />
                </Datagrid>
            </ReferenceArrayField>
        </SimpleShowLayout>
    </Show>
);

<ReferenceArrayField> reads the list of book_ids in the current record (an author in this example). Then, it uses dataProvider.getMany('books', { ids }) fetch the related books.

<ReferenceArrayField> creates a ListContext with the related records, so you can use any component relying on this context (<Datagrid>, <SimpleList>, etc.).

You can also use it in a List page:

const AuthorList = () => (
    <List>
        <Datagrid>
            <TextField source="first_name" />
            <TextField source="last_name" />
            <DateField source="date_of_birth" />
            <ReferenceArrayField reference="books" source="book_ids">
                <SingleFieldList>
                    <TextField source="title" />
                </SingleFieldList>
            </ReferenceArrayField>
        </Datagrid>
    </List>
);

Just like for <ReferenceField>, <ReferenceArrayField> aggregates and deduplicates all the renders made in a page, and creates an optimised request. So for the entire list of authors, it will make only one call to dataProvider.getMany('books', { ids }).

<ReferenceManyToManyField>

This Enterprise Edition field displays a many-to-many relationship implemented with two one-to-many relationships and a join table.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ books            β”‚       β”‚ book_authors β”‚      β”‚ authors       β”‚
β”‚------------------β”‚       β”‚--------------β”‚      β”‚---------------β”‚
β”‚ id               │───┐   β”‚ id           β”‚      β”‚ id            β”‚
β”‚ title            β”‚   └──╼│ book_id      β”‚   β”Œβ”€β”€β”‚ first_name    β”‚
β”‚ published_at     β”‚       β”‚ author_id    β”‚β•Ύβ”€β”€β”˜  β”‚ last_name     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚ is_public    β”‚      β”‚ date_of_birth β”‚
                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Here is how you would display the books of an author:

const AuthorShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="first_name" />
            <TextField source="last_name" />
            <DateField source="date_of_birth" />
            <ReferenceManyToManyField 
                reference="books"
                through="book_authors"
                using="author_id,book_id"
            >
                <Datagrid>
                    <TextField source="title" />
                    <DateField source="published_at" />
                </Datagrid>
            </ReferenceManyToManyField>
            <EditButton />
        </SimpleShowLayout>
    </Show>
);

And here is how you would display the authors of a book:

const BookShow = props => (
    <Show>
        <SimpleShowLayout>
            <TextField source="title" />
            <DateField source="published_at" />
            <ReferenceManyToManyField 
                reference="authors"
                through="book_authors"
                using="book_id,author_id"
            >
                <Datagrid>
                    <FunctionField 
                        label="Author"
                        render={record => `${record.first_name} ${record.last_name}`}
                    />
                    <DateField source="date_of_birth" />
                </Datagrid>
            </ReferenceManyToManyField>
            <EditButton />
        </SimpleShowLayout>
    </Show>
);

<ReferenceManyToManyField> creates a ListContext with the related records, so you can use any component relying on this context (<Datagrid>, <SimpleList>, etc.).

<ReferenceOneField>

This field fetches a one-to-one relationship, e.g. the details of a book, when using a foreign key.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ books        β”‚       β”‚ book_details β”‚
β”‚--------------β”‚       β”‚--------------β”‚
β”‚ id           │───┐   β”‚ id           β”‚
β”‚ title        β”‚   └──╼│ book_id      β”‚
β”‚ published_at β”‚       β”‚ genre        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚ ISBN         β”‚
                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Here is how to use it:

const BookShow = () => (
    <Show>
        <SimpleShowLayout>
            <TextField source="title" />
            <DateField source="published_at" />
            <ReferenceOneField label="Genre" reference="book_details" target="book_id">
                <TextField source="genre" />
            </ReferenceOneField>
            <ReferenceOneField label="ISBN" reference="book_details" target="book_id">
                <TextField source="ISBN" />
            </ReferenceOneField>
        </SimpleShowLayout>
    </Show>
);

<ReferenceOneField> behaves like <ReferenceManyField>: it uses the current record (a book in this example) to build a filter for the book details with the foreign key (book_id). Then, it uses dataProvider.getManyReference('book_details', { target: 'book_id', id: book.id }) to fetch the related details, and takes the first one.

<ReferenceOneField> creates a RecordContext with the reference record, so you can use any component relying on this context (<TextField>, <SimpleShowLayout>, etc.).

Tip: As with <ReferenceField>, you can call <ReferenceOneField> as many times as you need in the same component, react-admin will only make one call to dataProvider.getManyReference().

For the inverse relationships (the author linked to a biography), you can use a <ReferenceField>.