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_idfield)
- <ReferenceArrayField>when the API uses an array of foreign keys (e.g. each author has a- book_idsfield)
- <ArrayField>when the API embeds an array of records (e.g. each author has a- booksfield)
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_idfield)
- Deep Field Source, when the API embeds the related record (e.g. each book has an authorfield 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_detailhas a- book_idfield)
- <ReferenceField>when the API uses a reverse foreign key (e.g. each- bookhas a- book_detail_idfield)
- Deep Field Source, when the API embeds the related record (e.g. each book has a book_detailfield 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_authorstable with both- book_idand- author_idfields)
- <ReferenceArrayField>when the API uses an array of foreign keys (e.g. each author has a- book_idsfield, and each book has an- author_idsfield)
- <ArrayField>, when the API embeds an array of records (e.g. each author has a- booksfield, and each book has an- authorsfield)
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>.
