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">
 <DataTable>
 <DataTable.Col source="title" />
 <DataTable.Col source="published_at" field={DateField} />
 </DataTable>
 </ArrayField>
 </SimpleShowLayout>
 </Show>
);

<ArrayField> creates a ListContext with the embedded records, so you can use any component relying on this context (<DataTable>, <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>
 <DataTable>
 <DataTable.Col source="title" />
 <DataTable.Col source="published_at" field={DateField} />
 <DataTable.Col source="author_id" label="Author">
 <ReferenceField source="author_id" reference="authors">
 <FunctionField render={record => `${record.first_name}${record.last_name}`} />
 </ReferenceField>
 </DataTable.Col>
 <DataTable.Col source="author_id" label="Author DOB">
 <ReferenceField source="author_id" reference="authors">
 <DateField source="date_of_birth" />
 </ReferenceField>
 </DataTable.Col>
 </DataTable>
 </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">
 <DataTable>
 <DataTable.Col source="title" />
 <DataTable.Col source="published_at" field={DateField} />
 </DataTable>
 </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 (<DataTable>, <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">
 <DataTable>
 <DataTable.Col source="title" />
 <DataTable.Col source="published_at" field={DateField} />
 </DataTable>
 </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 (<DataTable>, <SimpleList>, etc.).

You can also use it in a List page:

const AuthorList = () => (
 <List>
 <DataTable>
 <DataTable.Col source="first_name" />
 <DataTable.Col source="last_name" />
 <DataTable.Col source="date_of_birth" field={DateField} />
 <DataTable.Col label="Books" source="book_ids">
 <ReferenceArrayField reference="books" source="book_ids">
 <SingleFieldList>
 <TextField source="title" />
 </SingleFieldList>
 </ReferenceArrayField>
 </DataTable.Col>
 </DataTable>
 </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 EditionReact Admin Enterprise Edition icon 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"
 >
 <DataTable>
 <DataTable.Col source="title" />
 <DataTable.Col source="published_at" field={DateField} />
 </DataTable>
 </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"
 >
 <DataTable>
 <DataTable.Col
 label="Author"
 render={record => `${record.first_name}${record.last_name}`}
 />
 <DataTable.Col source="date_of_birth" field={DateField} />
 </DataTable>
 </ReferenceManyToManyField>
 <EditButton />
 </SimpleShowLayout>
 </Show>
);

<ReferenceManyToManyField> creates a ListContext with the related records, so you can use any component relying on this context (<DataTable>, <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>.

AltStyle によって変換されたページ (->オリジナル) /