ra-relationships
A set of alternative inputs and fields to edit relationships, including many-to-many relationships using a join table.
Test it live in the Enterprise Edition Storybook.
Installation
npm install --save @react-admin/ra-relationships
# or
yarn add @react-admin/ra-relationships
Tip: ra-relationships
is part of the React-Admin Enterprise Edition, and hosted in a private npm registry. You need to subscribe to one of the Enterprise Edition plans to access this package.
The package contains new translation messages (in English and French). You should add them to your i18nProvider
:
import { Admin } from 'react-admin';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import englishMessages from 'ra-language-english';
import frenchMessages from 'ra-language-french';
import {
raRelationshipsLanguageEnglish,
raRelationshipsLanguageFrench,
} from '@react-admin/ra-relationships';
const messages = {
en: { ...englishMessages, ...raRelationshipsLanguageEnglish },
fr: { ...frenchMessages, ...raRelationshipsLanguageFrench },
};
const i18nProvider = polyglotI18nProvider(locale => messages[locale], 'en');
const App = () => <Admin i18nProvider={is18nProvider}>{/* ... */}</Admin>;
import { Admin } from "react-admin";
import polyglotI18nProvider from "ra-i18n-polyglot";
import englishMessages from "ra-language-english";
import frenchMessages from "ra-language-french";
import { raRelationshipsLanguageEnglish, raRelationshipsLanguageFrench } from "@react-admin/ra-relationships";
const messages = {
en: { ...englishMessages, ...raRelationshipsLanguageEnglish },
fr: { ...frenchMessages, ...raRelationshipsLanguageFrench },
};
const i18nProvider = polyglotI18nProvider((locale) => messages[locale], "en");
const App = () => <Admin i18nProvider={is18nProvider}>{/* ... */}</Admin>;
Many-To-Many Relationships
Developers usually store many-to-many relationships in databases using an associative table (also known as join table, junction table or cross-reference table). For instance, if a Book can have many Authors, and an Author can write several Books, the normalized way to store this relationship in a relational database uses an intermediate table book_authors
, as follows:
┌──────────────────┐ ┌──────────────┐ ┌───────────────┐
│ books │ │ book_authors │ │ authors │
│------------------│ │--------------│ │---------------│
│ id │───┐ │ id │ ┌──│ id │
│ title │ └──╼│ book_id │ │ │ first_name │
│ body │ │ author_id │╾──┘ │ last_name │
│ publication_date │ │ is_public │ │ date_of_birth │
└──────────────────┘ └──────────────┘ └───────────────┘
In the book_authors
table, book_id
and author_id
are both foreign keys to books
and authors
.
A REST API closely following this data model exposes the three resources /books
, /authors
, and /book_authors
. ra-relationships
components rely on the associative table without ever showing it to the end-user. From the end user's point of view, the associative table is an implementation detail.
Out of scope
If the associative table uses a composite primary key, then ra-relationships
does not work, as react-admin requires that all entities expose an identifier called id
. For example, if user permissions are seen as a many-to-many relationship, they can be modeled in a relational database as follows:
users user_permissions permissions
---------- ---------------- -----------------
login user_login key
password permission_key description
first_name
last_name
Here, the associative table uses a composite primary key made of the tuple (user_login, permission_key)
. To allow react-admin to use this associative table, the related API route (/user_permissions
) must include a unique id
field for each record (which can simply be the concatenation of the two foreign keys).
Also, if your REST API can present that relationship through a list of related record ids (e.g. author_ids
in books
and book_ids
in authors
), you don't need ra-relationships
. Just use <ReferenceArrayField>
and <ReferenceArrayInput>
, which are standard components in react-admin.
<ReferenceOneInput>
Use <ReferenceOneInput>
in an <Edit>
or <Create>
view to edit a record linked to the current record via a one-to-one relationship, e.g. to edit the details of a book in the book edition view.
<ReferenceOneInput>
renders the inputs provided as its children, and fetches the related record to populate them. When users change the related record fields, the <ReferenceOneInput>
component stores these changes locally. Then, when users actually submit the form, <ReferenceOneInput>
will update both the base record and the related record.
Usage
Here is an example one-to-one relationship: a book
has at most one book_details
row associated to it.
┌─────────────┐ ┌──────────────┐
│ book │ │ book_details │
│-------------│ │--------------│
│ id │───┐ │ id │
│ title │ └──╼│ book_id │
└─────────────┘ │ year │
│ author │
│ country │
│ genre │
│ pages │
└──────────────┘
You probably want to let users edit the book details directly from the book Edition view (instead of having to go to the book details Edition view). <ReferenceOneInput>
allows to do that.
import { Edit, SimpleForm, TextInput, NumberInput } from 'react-admin';
import { ReferenceOneInput } from '@react-admin/ra-relationships';
const BookEdit = () => (
<Edit mutationMode="optimistic">
<SimpleForm>
<TextInput source="title" />
<ReferenceOneInput reference="book_details" target="book_id">
<NumberInput source="year" />
<TextInput source="author" />
<TextInput source="country" />
<TextInput source="genre" />
<NumberInput source="pages" />
</ReferenceOneInput>
</SimpleForm>
</Edit>
);
<ReferenceOneInput>
requires a reference
and a target
prop to know which entity to fetch, and one or more inputs as its children
to edit the related record.
<ReferenceOneInput>
persists the changes in the referenced record (book details in the above example) after persisting the changes in the main resource (book in the above example). This means that you can also use <ReferenceOneInput>
in <Create>
views.
Tip: <ReferenceOneInput>
cannot be used with undoable
mutations. You have to set mutationMode="optimistic"
or mutationMode="pessimistic"
in the parent <Edit>
, as in the example above.
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
target |
Required | string |
- | Target field carrying the relationship on the referenced resource, e.g. 'book_id' |
reference |
Required | string |
- | The name of the resource for the referenced records, e.g. 'book_details' |
children |
Required | Element |
- | One or several input elements that accept a source prop |
label |
Optional | string |
reference |
Input label. In i18n apps, the label is passed to the translate function. Defaults to the humanized reference when omitted. Set label={false} to hide the label. |
source |
Optional | string |
id |
Name of the field that carries the identity of the current record, used as origin for the relationship |
filter |
Optional | Object |
- | Filters to use when fetching the related record, passed to getManyReference() |
sort |
Optional | { field, order } |
{ field: 'id', order: 'ASC' } |
Sort order to use when fetching the related record, passed to getManyReference() |
defaultValue |
Optional | Object |
- | Default value for the related record (in case it does not yet exist) |
sx |
Optional | SxProps |
- | MUI shortcut for defining custom styles |
queryOptions |
Optional | UseQueryOptions |
- | Options for the queries (getManyReferences ) |
mutationOptions |
Optional | UseMutationOptions |
- | Options for the mutations (create and update ) |
Additional props are passed to the MUI <Stack>
component.
children
<ReferenceOneInput>
expects input components as its children (like <TextInput>
, <NumberInput>
, etc.), which will allow to edit the related record. The inputs will be rendered inside an MUI <Stack>
.
<ReferenceOneInput reference="book_details" target="book_id">
<NumberInput source="year" />
<TextInput source="author" />
<TextInput source="country" />
<TextInput source="genre" />
<NumberInput source="pages" />
</ReferenceOneInput>
Important note: <ReferenceOneInput>
works by cloning its children and overriding their source
prop, to add a temporary field name prefix. This means that, if you need to nest your inputs inside another component, you need to propagate the source
prop to them.
defaultValue
<ReferenceOneInput>
allows to specify a default value for the related record. This is useful when the current record does not yet have a related record, and you want to pre-fill the related record with some default values.
<ReferenceOneInput
reference="book_details"
target="book_id"
defaultValue={{ author: 'Gustave Flaubert', year: 1857 }}
>
<NumberInput source="year" />
<TextInput source="author" />
<TextInput source="country" />
<TextInput source="genre" />
<NumberInput source="pages" />
</ReferenceOneInput>
filter
<ReferenceOneInput>
allows to specify filters to use when fetching the related record. This can be useful when you need additional filters to select the related record.
<ReferenceOneInput
reference="book_details"
target="book_id"
filter={{ reviewed: true }}
>
...
</ReferenceOneInput>
label
By default, <ReferenceOneInput>
humanizes the reference
name to build a label. You can customize the label by passing the label
prop.
<ReferenceOneInput
reference="book_details"
target="book_id"
label="Detailed information about the book"
>
...
</ReferenceOneInput>
React-admin uses the i18n system to translate the label, so you can use translation keys to have one label for each language supported by the interface:
<ReferenceOneInput
reference="book_details"
target="book_id"
label="resource.books.fields.details"
>
...
</ReferenceOneInput>
reference
The name of the resource to fetch for the related records.
For instance, if you want to display the book_details
of a given book
, the reference
name should be book_details
:
<ReferenceOneInput reference="book_details" target="book_id">
...
</ReferenceOneInput>
sort
<ReferenceOneInput>
allows to specify the sort options used when fetching the related record. This can be useful when the relation table does not have an id
column.
<ReferenceOneInput
reference="book_details"
target="book_id"
sort={{ field: '_id', order: 'DESC' }}
>
...
</ReferenceOneInput>
source
By default, <ReferenceManyInput>
fetches the reference
for which the target
field equals the current record id
. You can customize the field that carries the identity of the current record by setting the source
prop.
<ReferenceOneInput reference="book_details" target="book_id" source="_id">
...
</ReferenceOneInput>
sx
You can override the style of the root component (a MUI <FormControl>
) and its child components by setting the sx
prop.
<ReferenceOneInput
reference="book_details"
target="book_id"
sx={{ border: '1px solid red' }}
>
...
</ReferenceOneInput>
target
Name of the field carrying the relationship on the referenced resource. For instance, if each book
is linked to a record in book_details
, and each book_details
exposes a book_id
field linking to the book
, the target
would be book_id
.
<ReferenceOneInput reference="book_details" target="book_id">
...
</ReferenceOneInput>
queryOptions
Use the queryOptions
prop to pass options to the dataProvider.getManyReferences()
query that fetches the possible choices.
For instance, to pass a custom meta:
<ReferenceOneInput
reference="book_details"
target="book_id"
queryOptions={{ meta: { foo: 'bar' } }}
/>
<ReferenceOneInput reference="book_details" target="book_id" queryOptions={{ meta: { foo: "bar" } }} />;
mutationOptions
Use the mutationOptions
prop to pass options to the dataProvider.create()
and dataProvider.update()
mutations.
For instance, to pass a custom meta:
<ReferenceOneInput
reference="book_details"
target="book_id"
mutationOptions={{ meta: { foo: 'bar' } }}
/>
<ReferenceOneInput reference="book_details" target="book_id" mutationOptions={{ meta: { foo: "bar" } }} />;
Customizing The Child Inputs
<ReferenceOneInput>
works by cloning its children and overriding their source
prop, to add a temporary field name prefix. This means that, if you need to nest your inputs inside another component, you will need to propagate the source
prop to them.
In this example, the <TextInput>
component is wrapped inside a <MyCustomInput>
component. That adds an icon and additional styling.
import AccountCircle from '@mui/icons-material/AccountCircle';
import AutoStoriesIcon from '@mui/icons-material/AutoStories';
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
import ClassIcon from '@mui/icons-material/Class';
import LanguageIcon from '@mui/icons-material/Language';
import { Box, SxProps } from '@mui/material';
import * as React from 'react';
import { TextInput } from 'react-admin';
import { ReferenceOneInput } from '@react-admin/ra-relationships';
const MyCustomInput = ({
source,
icon: Icon,
}: {
source: string;
icon: React.FunctionComponent<{ sx?: SxProps }>;
}) => (
<Box sx={{ display: 'flex', alignItems: 'flex-end' }}>
<Icon sx={{ color: 'action.active', mr: 1.5, my: 1 }} />
<TextInput
source={source} // Propagate the source prop to the real input
variant="standard"
size="small"
helperText={false}
/>
</Box>
);
export const CustomInputs = () => (
<ReferenceOneInput reference="book_details" target="book_id">
<MyCustomInput source="year" icon={CalendarMonthIcon} />
<MyCustomInput source="author" icon={AccountCircle} />
<MyCustomInput source="country" icon={LanguageIcon} />
<MyCustomInput source="genre" icon={ClassIcon} />
<MyCustomInput source="pages" icon={AutoStoriesIcon} />
</ReferenceOneInput>
);
import AccountCircle from "@mui/icons-material/AccountCircle";
import AutoStoriesIcon from "@mui/icons-material/AutoStories";
import CalendarMonthIcon from "@mui/icons-material/CalendarMonth";
import ClassIcon from "@mui/icons-material/Class";
import LanguageIcon from "@mui/icons-material/Language";
import { Box } from "@mui/material";
import * as React from "react";
import { TextInput } from "react-admin";
import { ReferenceOneInput } from "@react-admin/ra-relationships";
const MyCustomInput = ({ source, icon: Icon }) => (
<Box sx={{ display: "flex", alignItems: "flex-end" }}>
<Icon sx={{ color: "action.active", mr: 1.5, my: 1 }} />
<TextInput
source={source} // Propagate the source prop to the real input
variant="standard"
size="small"
helperText={false}
/>
</Box>
);
export const CustomInputs = () => (
<ReferenceOneInput reference="book_details" target="book_id">
<MyCustomInput source="year" icon={CalendarMonthIcon} />
<MyCustomInput source="author" icon={AccountCircle} />
<MyCustomInput source="country" icon={LanguageIcon} />
<MyCustomInput source="genre" icon={ClassIcon} />
<MyCustomInput source="pages" icon={AutoStoriesIcon} />
</ReferenceOneInput>
);
Limitations
<ReferenceOneInput>
cannot be used inside an<ArrayInput>
or a<ReferenceManyInput>
.<ReferenceOneInput>
cannot have a<ReferenceManyInput>
or a<ReferenceManyToManyInput>
as one of its children.<ReferenceOneInput>
does not support server side validation.
<ReferenceManyInput>
Use <ReferenceManyInput>
in an <Edit>
or <Create>
view to edit one-to-many relationships, e.g. to edit the variants of a product in the product edition view.
<ReferenceManyInput>
fetches the related records, and renders them in a sub-form. When users add, remove of update related records, the <ReferenceManyInput>
component stores these changes locally. When the users actually submit the form, <ReferenceManyInput>
computes a diff with the existing relationship, and sends the related changes (additions, deletions, and updates) to the server.
Usage
An example one-to-many relationship can be found in ecommerce systems: a product has many variants.
┌───────────────┐ ┌──────────────┐
│ products │ │ variants │
│---------------│ │--------------│
│ id │───┐ │ id │
│ name │ └──╼│ product_id │
│ price │ │ sku │
│ category_id │ │ size │
└───────────────┘ │ color │
│ stock │
└──────────────┘
You probably want to let users edit variants directly from the product Edition view (instead of having to go to the variant Edition view). <ReferenceManyInput>
allows to do that.
import {
Edit,
SimpleForm,
TextInput,
NumberInput,
ReferenceInput,
SelectInput,
} from 'react-admin';
import { ReferenceManyInput } from '@react-admin/ra-relationships';
const ProductEdit = () => (
<Edit mutationMode="optimistic">
<SimpleForm>
<TextInput source="name" />
<NumberInput source="price" />
<ReferenceInput source="category_id" reference="categories" />
<ReferenceManyInput reference="variants" target="product_id">
<SimpleFormIterator inline>
<TextInput source="sku" />
<SelectInput source="size" choices={sizes} />
<SelectInput source="color" choices={colors} />
<NumberInput source="stock" defaultValue={0} />
</SimpleFormIterator>
</ReferenceManyInput>
</SimpleForm>
</Edit>
);
<ReferenceManyInput>
requires a reference
and a target
prop to know which entity to fetch, and a child component (usually a <SimpleFormIterator>
) to edit the relationship.
<ReferenceManyInput>
persists the changes in the reference records (variants in the above example) after persisting the changes in the main resource (product in the above example). This means that you can also use <ReferenceManyInput>
in <Create>
views.
Tip: <ReferenceManyInput>
cannot be used with undoable
mutations. You have to set mutationMode="optimistic"
or mutationMode="pessimistic"
in the parent <Edit>
or <Create>
, as in the example above.
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
target |
Required | string |
- | Target field carrying the relationship on the referenced resource, e.g. 'user_id' |
reference |
Required | string |
- | The name of the resource for the referenced records, e.g. 'books' |
children |
Required | Element |
- | One or several elements that render a list of records based on a ListContext |
label |
Optional | string |
reference |
Input label. In i18n apps, the label is passed to the translate function. Defaults to the humanized reference when omitted. Set label={false} to hide the label. |
helperText |
Optional | string |
- | Text to be displayed under the input |
source |
Optional | string |
id |
Name of the field that carries the identity of the current record, used as origin for the relationship |
filter |
Optional | Object |
- | Filters to use when fetching the related records, passed to getManyReference() |
perPage |
Optional | number |
25 | Maximum number of referenced records to fetch |
sort |
Optional | { field, order } |
{ field: 'id', order: 'DESC' } |
Sort order to use when fetching the related records, passed to getManyReference() |
defaultValue |
Optional | array |
- | Default value of the input. |
validate |
Optional | Function | array |
- | Validation rules for the array. See the Validation Documentation for details. |
sx |
Optional | SxProps |
- | MUI shortcut for defining custom styles |
queryOptions |
Optional | UseQueryOptions |
- | Options for the queries (getManyReferences ) |
mutationOptions |
Optional | UseMutationOptions |
- | Options for the mutations (create , update and delete ) |
children
<ReferenceManyInput>
creates an <ArrayInputContext>
, so it accepts the same type of children as <ArrayInput>
: a Form iterator. React-admin bundles one such iterator: <SimpleFormIterator>
. It renders one row for each related record, giving the user the ability to add, remove, or edit related records.
<ReferenceManyInput reference="variants" target="product_id">
<SimpleFormIterator>
<TextInput source="sku" />
<SelectInput source="size" choices={sizes} />
<SelectInput source="color" choices={colors} />
<NumberInput source="stock" defaultValue={0} />
</SimpleFormIterator>
</ReferenceManyInput>
Check out the <SimpleFormIterator>
documentation for more details.
defaultValue
When the current record has no related records, <ReferenceManyInput>
renders an empty list with an "Add" button to add related records.
You can use the defaultValue
prop to populate the list of related records in that case. It must be an array of objects.
<ReferenceManyInput
reference="variants"
target="product_id"
defaultValue={[
{ sku: 'SKU_1', size: 'S', color: 'black', stock: 0 },
{ sku: 'SKU_2', size: 'M', color: 'black', stock: 0 },
{ sku: 'SKU_3', size: 'L', color: 'black', stock: 0 },
{ sku: 'SKU_4', size: 'XL', color: 'black', stock: 0 },
]}
>
<SimpleFormIterator>
<TextInput source="sku" />
<SelectInput source="size" choices={sizes} />
<SelectInput source="color" choices={colors} />
<NumberInput source="stock" defaultValue={0} />
</SimpleFormIterator>
</ReferenceManyInput>
filter
You can filter the query used to populate the current values. Use the filter
prop for that.
<ReferenceManyInput
reference="variants"
target="product_id"
filter={{ is_published: true }}
>
...
</ReferenceManyInput>
helperText
If you need to display a text below the input (usually to explain the expected data to the user), use the helperText
prop.
<ReferenceManyInput
reference="variants"
target="product_id"
helperText="Enter at least 5 variants for each product"
>
...
</ReferenceManyInput>
label
By default, <ReferenceManyInput>
humanizes the reference
name to build a label. You can customize the label by passing the label
prop.
<ReferenceManyInput
reference="variants"
target="product_id"
label="Product variants"
>
...
</ReferenceManyInput>
React-admin uses the i18n system to translate the label, so you can use translation keys to have one label for each language supported by the interface:
<ReferenceManyInput
reference="variants"
target="product_id"
label="resource.products.fields.variants"
>
...
</ReferenceManyInput>
perPage
By default, react-admin restricts the possible values to 25 and displays no pagination control. You can change the limit by setting the perPage
prop:
<ReferenceManyInput reference="variants" target="product_id" perPage={10}>
...
</ReferenceManyInput>
reference
The name of the resource to fetch for the related records.
For instance, if you want to display the variants
of a given product
, the reference
name should be variants
:
<ReferenceManyInput reference="books" target="author_id">
...
</ReferenceManyInput>
sort
By default, related records appear ordered by id desc. You can change this order by setting the sort
prop (an object with field
and order
properties).
<ReferenceManyInput
reference="variants"
target="product_id"
sort={{ field: 'sku', order: 'ASC' }}
>
...
</ReferenceManyField>
source
By default, <ReferenceManyInput>
fetches the references
for which the target
field equals the current record id
. You can customize the field that carries the identity of the current record by setting the source
prop.
<ReferenceManyInput reference="variants" target="product_id" source="_id">
...
</ReferenceManyInput>
sx
You can override the style of the root component (a MUI <FormControl>
) and its child components by setting the sx
prop.
<ReferenceManyInput
reference="variants"
target="product_id"
sx={{ marginLeft: 2 }}
>
...
</ReferenceManyField>
target
Name of the field carrying the relationship on the referenced resource. For instance, if a product
has many variants
, and each variant resource exposes an product_id
field, the target
would be author_id
.
<ReferenceManyInput reference="variants" target="product_id">
...
</ReferenceManyInput>
validate
Just like regular inputs, you can use the validate
prop to define custom validation rules for the list of references.
import { minLength } from 'react-admin';
const ProductEdit = () => (
<Edit mutationMode="optimistic">
<SimpleForm>
<TextInput source="name" />
<ReferenceInput source="category_id" reference="categories" />
<ReferenceManyInput
reference="variants"
target="product_id"
validate={[minLength(2, 'Please add at least 2 variants')]}
>
...
</ReferenceManyInput>
</SimpleForm>
</Edit>
);
Limitations
<ReferenceManyInput>
cannot be used inside an<ArrayInput>
or a<ReferenceOneInput>
.<ReferenceManyInput>
cannot have a<ReferenceOneInput>
or a<ReferenceManyToManyInput>
as one of its children.<ReferenceManyInput>
does not support server side validation.
<ReferenceManyToManyField>
This component fetches a list of referenced records by lookup in an associative table and passes the records down to its child component, which must be an iterator component.
Note: The <ReferenceManyToManyField>
cannot currently display multiple records with the same id from the end reference resource, even though they might have different properties in the associative table.
Usage
Let's imagine that you're writing an app managing concerts for artists. The data model features a many-to-many relationship between the bands
and venues
tables through a performances
associative table.
┌─────────┐ ┌──────────────┐ ┌───────────────┐
│ bands │ │ performances │ │ venues │
│---------│ │--------------│ │---------------│
│ id │───┐ │ id │ ┌──│ id │
│ name │ └──╼│ band_id │ │ │ name │
│ │ │ venue_id │╾──┘ │ location │
│ │ │ date │ │ │
└─────────┘ └──────────────┘ └───────────────┘
In this example, bands.id
matches performances.band_id
, and performances.venue_id
matches venues.id
.
To allow users see the venues
for a given band
in <SingleFieldList>
, wrap that component in <ReferenceManyToManyField>
where you define the relationship via the reference
, through
and using
props:
import React from 'react';
import {
Show,
SimpleShowLayout,
TextField,
DateField,
SingleFieldList,
ChipField,
} from 'react-admin';
import { ReferenceManyToManyField } from '@react-admin/ra-relationships';
export const BandShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="name" />
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
label="Performances"
>
<SingleFieldList>
<ChipField source="name" />
</SingleFieldList>
</ReferenceManyToManyField>
<EditButton />
</SimpleShowLayout>
</Show>
);
import React from "react";
import { Show, SimpleShowLayout, TextField, SingleFieldList, ChipField } from "react-admin";
import { ReferenceManyToManyField } from "@react-admin/ra-relationships";
export const BandShow = () => (
<Show>
<SimpleShowLayout>
<TextField source="name" />
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
label="Performances"
>
<SingleFieldList>
<ChipField source="name" />
</SingleFieldList>
</ReferenceManyToManyField>
<EditButton />
</SimpleShowLayout>
</Show>
);
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
children |
Required | element |
- | An iterator element (e.g. <SingleFieldList> or <Datagrid> ). The iterator element usually has one or more child <Field> components. |
reference |
Required | string |
- | Name of the reference resource, e.g. 'venues' |
through |
Required | string |
- | Name of the resource for the associative table, e.g. 'performances' |
filter |
Optional | object |
{} |
Filter for the associative table (passed to the getManyReference() call) |
joinLimit |
Optional | number |
100 | Limit for the number of results fetched from the associative table. Should be greater than perPage |
perPage |
Optional | number |
25 | Limit the number of displayed result after getManyReference is called. Useful when using a pagination component. Should be smaller than joinLimit |
sort |
Optional | { field: string, order: 'ASC' or 'DESC' } |
{ field: 'id', order: 'DESC' } |
Sort for the associative table (passed to the getManyReference() call) |
source |
Optional | string |
'id' |
Name of the field containing the identity of the main resource. Used determine the value to look for in the associative table. |
using |
Optional | string |
'[resource]_id,[reference]_id' |
Tuple (comma separated) of the two field names used as foreign keys, e.g 'band_id,venue_id'. The tuple should start with the field pointing to the resource, and finish with the field pointing to the reference |
children
<ReferenceManyToManyField>
expects an iterator component as child, i.e. a component working inside a ListContext
.
This means you can use a <Datagrid>
instead of a <SingleFieldList>
, which is useful if you want to display more details about related records. For instance, to display the venue name
and location
:
export const BandShow = (props) => (
<Show {...props}>
<SimpleShowLayout>
<TextField source="name" />
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
label="Performances"
>
- <SingleFieldList>
- <ChipField source="name" />
- </SingleFieldList>
+ <Datagrid>
+ <TextField source="name" />
+ <TextField source="location" />
+ </Datagrid>
</ReferenceManyToManyField>
<EditButton />
</SimpleShowLayout>
</Show>
);
filter
You can filter the records of the associative table (e.g. performances
) using the filter
prop. This filter
is passed to the getManyReference()
call.
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
filter={{ date: '2018-08-31' }}
>
{/* ... */}
</ReferenceManyToManyField>
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
filter={{ date: "2018-08-31" }}
>
{/* ... */}
</ReferenceManyToManyField>;
joinLimit
By default, react-admin fetches 100 entries in the join table (e.g. performances
). You can decrease or increase the number of entries fetched from the associative table by modifying the joinLimit
prop:
import { Pagination } from 'react-admin';
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
joinLimit={50}
>
{/* ... */}
</ReferenceManyToManyField>;
<ReferenceManyToManyField reference="venues" through="performances" using="band_id,venue_id" joinLimit={50}>
{/* ... */}
</ReferenceManyToManyField>;
export {};
perPage
By default, react-admin displays at most 25 entries from the associative table (e.g. 25 performances
). You can change the limit by setting the perPage
prop:
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
perPage={10}
>
{/* ... */}
</ReferenceManyToManyField>
<ReferenceManyToManyField reference="venues" through="performances" using="band_id,venue_id" perPage={10}>
{/* ... */}
</ReferenceManyToManyField>;
Note: You can add a pagination system by adding the <Pagination>
component to the <ReferenceManyToManyField>
children:
import { Pagination } from 'react-admin';
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
perPage={10}
>
{/* ... */}
<Pagination />
</ReferenceManyToManyField>;
import { Pagination } from "react-admin";
<ReferenceManyToManyField reference="venues" through="performances" using="band_id,venue_id" perPage={10}>
{/* ... */}
<Pagination />
</ReferenceManyToManyField>;
reference
The name of the target resource to fetch.
For instance, if you want to display the venues
of a given bands
, through performances
, the reference
name should be venues
:
<ReferenceManyToManyField
source="id"
reference="venues"
resource="bands"
through="performances"
>
{/* ... */}
</ReferenceManyToManyField>
<ReferenceManyToManyField source="id" reference="venues" resource="bands" through="performances">
{/* ... */}
</ReferenceManyToManyField>;
sort
By default, react-admin orders the possible values by id
desc for the associative table (e.g. performances
). You can change this order by setting the sort
prop (an object with field
and order
properties) to be applied to the associative resource.
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
sort={{ field: 'id', order: 'DESC' }}
>
{/* ... */}
</ReferenceManyToManyField>
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
sort={{ field: "id", order: "DESC" }}
>
{/* ... */}
</ReferenceManyToManyField>;
source
By default, <ReferenceManyToManyField>
uses the id
field as target for the reference. If the foreign key points to another field of your record, you can select it with the source
prop
<ReferenceManyToManyField
source="_id"
reference="venues"
resource="bands"
through="performances"
>
{/* ... */}
</ReferenceManyToManyField>
<ReferenceManyToManyField source="_id" reference="venues" resource="bands" through="performances">
{/* ... */}
</ReferenceManyToManyField>;
through
You must specify the associative table name using the through
prop.
<ReferenceManyToManyField reference="venues" through="performances">
{/* ... */}
</ReferenceManyToManyField>
<ReferenceManyToManyField reference="venues" through="performances">
{/* ... */}
</ReferenceManyToManyField>;
using
You can specify the columns to use in the associative using
the using prop.
<ReferenceManyToManyField
reference="venues"
through="performances"
using="band_id,venue_id"
>
{/* ... */}
</ReferenceManyToManyField>
<ReferenceManyToManyField reference="venues" through="performances" using="band_id,venue_id">
{/* ... */}
</ReferenceManyToManyField>;
DataProvider Calls
<ReferenceManyToManyField>
fetches the dataProvider
twice in a row:
- once to get the records of the associative resource (
performances
in this case), using agetManyReference()
call - once to get the records of the reference resource (
venues
in this case), using agetMany()
call.
For instance, if the user displays the band of id 123
, <ReferenceManyToManyField>
first issues the following query to the dataProvider
:
dataProvider.getManyReference('performances', {
target: 'band_id',
id: 123,
});
Let's say that the dataProvider
returns the following response:
{
"data": [
{ "id": 667, "band_id": 123, "venue_id": 732 },
{ "id": 895, "band_id": 123, "venue_id": 874 }
{ "id": 901, "band_id": 123, "venue_id": 756 }
],
"total": 3
}
Then, <ReferenceManyToManyField>
issues a second query to the dataProvider
:
dataProvider.getMany('venues', {
ids: [732, 874, 756],
});
And receives the reference venues:
{
"data": [
{ "id": 732, "name": "Madison Square Garden" },
{ "id": 874, "name": "Yankee Stadium" }
{ "id": 874, "name": "Barclays Center" }
],
"total": 3
}
<ReferenceManyToManyInput>
This component allows adding or removing relationships between two resources sharing an associative table. The changes in the associative table are sent to the dataProvider when the user submits the form so that they can cancel the changes before submission.
Note: The <ReferenceManyToManyInput>
cannot currently display multiple records with the same id from the end reference resource even though they might have different properties in the associative table.
Usage
Let's imagine that you're writing an app managing concerts for artists. The data model features a many-to-many relationship between the bands
and venues
tables through a performances
associative table.
┌─────────┐ ┌──────────────┐ ┌───────────────┐
│ bands │ │ performances │ │ venues │
│---------│ │--------------│ │---------------│
│ id │───┐ │ id │ ┌──│ id │
│ name │ └──╼│ band_id │ │ │ name │
│ │ │ venue_id │╾──┘ │ location │
│ │ │ date │ │ │
└─────────┘ └──────────────┘ └───────────────┘
In this example, bands.id
matches performances.band_id
, and performances.venue_id
matches venues.id
.
To let users edit the venues
for given band
in an <AutocompleteArrayInput>
, wrap that input in a <ReferenceManyToManyInput>
where you define the relationship via the reference
, through
and using
props:
import {
AutocompleteArrayInput,
Edit,
SimpleForm,
TextInput,
} from 'react-admin';
import { ReferenceManyToManyInput } from '@react-admin/ra-relationships';
export const BandEdit = () => (
<Edit mutationMode="optimistic">
<SimpleForm>
<TextInput source="name" />
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
>
<AutocompleteArrayInput
label="Performances"
optionText="name"
/>
</ReferenceManyToManyInput>
</SimpleForm>
</Edit>
);
import { AutocompleteArrayInput, Edit, SimpleForm, TextInput } from "react-admin";
import { ReferenceManyToManyInput } from "@react-admin/ra-relationships";
export const BandEdit = () => (
<Edit mutationMode="optimistic">
<SimpleForm>
<TextInput source="name" />
<ReferenceManyToManyInput reference="venues" through="performances" using="band_id,venue_id">
<AutocompleteArrayInput label="Performances" optionText="name" />
</ReferenceManyToManyInput>
</SimpleForm>
</Edit>
);
<ReferenceManyToManyInput>
expects a child that is an input allowing to select multiple values as child - like <AutocompleteArrayInput>
in the example above. Other possible children are <SelectArrayInput>
, <CheckboxGroupInput>
, and <DualListInput>
.
Note that although all possible child components support a defaultValue
prop, it will only be applied on create views.
Tip: We don't recommend using <ReferenceManyToManyInput>
in an Edit
view that has its mutationMode
set to undoable
. Indeed, even if users cancel the main mutation, the changes in the associative table will still be applied.
Tip: If you need to edit the fields of the associative table (e.g. the date
in performances
), you can use a <ReferenceManyInput>
instead of <ReferenceManyToManyInput>
.
You will need to let users select the related record (venue
in the example above) via a <ReferenceInput>
:
import {
DateInput,
Edit,
ReferenceInput,
SelectInput,
SimpleForm,
SimpleFormIterator,
TextInput,
required,
} from 'react-admin';
import { ReferenceManyInput } from '@react-admin/ra-relationships';
const BandEdit = () => (
<Edit mutationMode="optimistic">
<SimpleForm>
<TextInput source="name" />
<ReferenceManyInput reference="performances" target="band_id">
<SimpleFormIterator inline>
<DateInput source="date" />
<ReferenceInput reference="venues" source="venue_id">
<SelectInput optionText="name" />
</ReferenceInput>
</SimpleFormIterator>
</ReferenceManyInput>
</SimpleForm>
</Edit>
);
Limitation: <ReferenceManyToManyInput>
cannot be used to filter a list.
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
children |
Required | element |
- | A select array input element (e.g. <SelectArrayInput> ). |
reference |
Required | string |
- | Name of the reference resource, e.g. 'venues' |
through |
Required | string |
- | Name of the resource for the associative table, e.g. 'book_authors' |
filter |
Optional | object |
{} |
Filter for the associative table (passed to the getManyReference() call) |
filter Choices |
Optional | object |
{} |
Filter for the possible choices fetched from the reference table (passed to the getList() call) |
perPage |
Optional | number |
25 | Limit for the number of results fetched from the associative table |
perPage Choices |
Optional | number |
25 | Limit for the number of possible choices fetched from the reference table |
sort |
Optional | { field: string, order: 'ASC' or 'DESC' } |
{ field: 'id', order: 'DESC' } |
Sort for the associative table (passed to the getManyReference() call) |
sort Choices |
Optional | { field: string, order: 'ASC' or 'DESC' } |
{ field: 'id', order: 'DESC' } |
Sort for the possible choices fetched from the reference table (passed to the getList() call) |
source |
Optional | string |
'id' |
Name of the field containing the identity of the main resource. Used determine the value to look for in the associative table. |
using |
Optional | string |
'([resource]_id,[reference]_id)' |
Tuple (comma separated) of the two field names used as foreign keys, e.g 'book_id,author_id'. The tuple should start with the field pointing to the resource, and finish with the field pointing to the reference |
children
<ReferenceManyToManyInput>
expects an select component as child, i.e. a component working inside a ChoiceContext
. That means you can use a <SelectArrayInput>
, or a <AutocompleteArrayInput>
.
For instance, to allow user to choose performances
using a <SelectArrayInput>
instead of an <AutocompleteArrayInput>
, you can write:
import React from 'react';
- import { Edit, AutocompleteArrayInput, SimpleForm, TextInput } from 'react-admin';
+ import { Edit, SelectArrayInput, SimpleForm, TextInput } from 'react-admin';
import { ReferenceManyToManyInput } from '@react-admin/ra-relationships';
export const BandEdit = () => (
<Edit mutationMode="pessimistic">
<SimpleForm>
<TextInput source="name" />
<ReferenceManyToManyInput
resource="bands"
reference="venues"
through="performances"
using="band_id,venue_id"
>
- <AutocompleteArrayInput
- label="Performances"
- optionText="name"
- />
+ <SelectArrayInput label="Performances" />
</ReferenceManyToManyInput>
</SimpleForm>
</Edit>
);
filter
You can filter the records of the associative table (e.g. performances
) using the filter
prop. This filter
is passed to the getManyReference()
call.
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
filter={{ date: '2018-08-31' }}
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
filter={{ date: "2018-08-31" }}
>
{/* ... */}
</ReferenceManyToManyInput>;
filterChoices
<ReferenceManyToManyInput>
displays a list of possible values from the reference table (e.g. venues
) as suggestions in the input. It uses the getList()
dataProvider call to fetch these possible values.
You can filter the possible values of the reference table using the filterChoices
prop. This filterChoices
is passed to the getList()
call.
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
filterChoice={{ location: 'New York' }}
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
filterChoice={{ location: "New York" }}
>
{/* ... */}
</ReferenceManyToManyInput>;
perPage
By default, react-admin displays at most 25 entries from the associative table (e.g. 25 performances
). You can change the limit by setting the perPage
prop:
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
perPage={10}
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput reference="venues" through="performances" using="band_id,venue_id" perPage={10}>
{/* ... */}
</ReferenceManyToManyInput>;
perPageChoices
<ReferenceManyToManyInput>
displays a list of possible values from the reference table (e.g. venues
) as suggestions in the input. It uses the getList()
dataProvider call to fetch these possible values.
By default, react-admin displays at most 25 possible values from the reference table (e.g. 25 venues
). You can change the limit by setting the perPageChoices
prop:
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
perPageChoices={10}
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput reference="venues" through="performances" using="band_id,venue_id" perPageChoices={10}>
{/* ... */}
</ReferenceManyToManyInput>;
reference
The name of the target resource to fetch.
For instance, if you want to display the venues of a given bands, through performances, the reference name should be venues:
<ReferenceManyToManyInput
source="id"
reference="venues"
resource="bands"
through="performances"
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput source="id" reference="venues" resource="bands" through="performances">
{/* ... */}
</ReferenceManyToManyInput>;
sort
By default, react-admin orders the possible values by id
desc for the associative table (e.g. performances
). You can change this order by setting the sort
prop (an object with field
and order
properties) to be applied to the associative resource.
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
sort={{ field: 'id', order: 'DESC' }}
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
sort={{ field: "id", order: "DESC" }}
>
{/* ... */}
</ReferenceManyToManyInput>;
sortChoices
By default, react-admin orders the possible values by id
desc for the reference table (e.g. venues
). You can change this order by setting the sortChoices
prop (an object with field
and order
properties).
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
sortChoices={{ field: 'id', order: 'DESC' }}
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
sortChoices={{ field: "id", order: "DESC" }}
>
{/* ... */}
</ReferenceManyToManyInput>;
source
By default, ReferenceManyToManyField uses the id
field as target for the reference. If the foreign key points to another field of your record, you can select it with the source
prop:
<ReferenceManyToManyInput
source="_id"
reference="venues"
resource="bands"
through="performances"
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput source="_id" reference="venues" resource="bands" through="performances">
{/* ... */}
</ReferenceManyToManyInput>;
through
You must specify the associative table name using the through
prop.
<ReferenceManyToManyInput reference="venues" through="performances">
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput reference="venues" through="performances">
{/* ... */}
</ReferenceManyToManyInput>;
using
You can specify the columns to use in the associative using the using
prop.
<ReferenceManyToManyInput
reference="venues"
through="performances"
using="band_id,venue_id"
>
{/* ... */}
</ReferenceManyToManyInput>
<ReferenceManyToManyInput reference="venues" through="performances" using="band_id,venue_id">
{/* ... */}
</ReferenceManyToManyInput>;
Limitations
<ReferenceManyToManyInput>
cannot be used inside an<ArrayInput>
, a<ReferenceOneInput>
or a<ReferenceManyInput>
.<ReferenceManyToManyInput>
does not support server side validation.
dataProvider
Calls
When rendered, <ReferenceManyToManyInput>
fetches the dataProvider
three times in a row:
- once to get the records of the associative resource (
performances
in this case), using agetManyReference()
call - once to get the records of the reference resource (
venues
in this case), using agetMany()
call. - once to get the possible values of the reference resource (
venues
in this case) to show as suggestions in the input, using agetList()
call
For instance, if the user edits the band of id 123
, <ReferenceManyToManyInput>
first issues the following query to the dataProvider
:
dataProvider.getManyReference('venues', {
target: 'band_id',
id: 123,
});
Let's say that the dataProvider
returns the following response:
{
"data": [
{ "id": 667, "band_id": 123, "venue_id": 732 },
{ "id": 895, "band_id": 123, "venue_id": 874 }
{ "id": 901, "band_id": 123, "venue_id": 756 }
],
"total": 3
}
Then, <ReferenceManyToManyInput>
issues a second query to the dataProvider
:
dataProvider.getMany('venues', {
ids: [732, 874, 756],
});
Which returns the following:
{
"data": [
{ "id": 732, "name": "Madison Square Garden" },
{ "id": 874, "name": "Yankee Stadium" }
{ "id": 874, "name": "Barclays Center" }
]
}
That's enough to display the current value in the input. But to display venues suggestions, the component makes a final call:
dataProvider.getList('venues', {
sort: { field: 'id', order: 'DESC' },
pagination: { page: 1, perPage: 25 },
filter: {},
});
{
"data": [
{ "id": 732, "name": "Madison Square Garden" },
{ "id": 874, "name": "Yankee Stadium" }
{ "id": 874, "name": "Barclays Center" }
...
],
"total": 32
}
And that's it for the display phase.
When the user submits the form, the save
function compares the value of the <ReferenceManyToManyInput>
(the list of relationships edited by the user) with the value previously returned by the dataProvider
. Using a diffing algorithm, it deduces a list of insertions and deletions in the associative table, that are executed all at once.
For instance, let's say that after displaying the venues 732 and 874 where bands 123 performs, the user removes venue 732, and adds venues 2 and 3. Upon submission, the dataProvider
will detect removals and additions, and send the following queries:
dataProvider.delete('performances', {
id: 667,
previousData: { id: 667, band_id: 123, venue_id: 732 },
});
dataProvider.create('performances', {
data: { band_id: 123, venue_id: 2 },
});
dataProvider.create('performances', {
data: { band_id: 123, venue_id: 3 },
});
<DualListInput>
To let users choose multiple values by moving them from a list of available choices to a list of selected choices, use a <DualListInput>
. It renders using two MUI's <List>. Set the choices
prop to determine the options (with id
, name
tuples):
const choices = [
{ id: 'programming', name: 'Programming' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'photography', name: 'Photography' },
];
<DualListInput source="tags" choices={choices} />;
const choices = [
{ id: "programming", name: "Programming" },
{ id: "lifestyle", name: "Lifestyle" },
{ id: "photography", name: "Photography" },
];
<DualListInput source="tags" choices={choices} />;
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
choices |
Required | Object[] |
- | List of items to show as options |
emptyText |
Optional | string |
'' | The text to display for the empty option |
optionText |
Optional | string | Function |
name |
Fieldname of record to display in the suggestion item, function which accepts the current record as argument ((record)=> {string} ) or an element which will be cloned with a record prop |
optionValue |
Optional | string |
id |
Fieldname of record containing the value to use as input value |
disableValue |
Optional | string |
disabled |
Fieldname of record containing the value to use to determine if an item should be disabled |
translateChoice |
Optional | boolean |
true |
Whether the choices should be translated |
addButton |
Optional | 'outlined' | 'contained' | 'text' | element |
- | A MUI variant value for the add button or a React element to replace it. See documentation below for more details |
removeButton |
Optional | 'outlined' | 'contained' | 'text' | element |
- | A MUI variant value for the remove button or a React element to replace it. See documentation below for more details |
addButtonLabel |
Optional | string |
ra-relationships.duallistinput.select |
The text or translation key to use as the label for the add button |
removeButtonLabel |
Optional | string |
ra-relationships.duallistinput.unselect |
The text or translation key to use as the label for the remove button |
availableItemsLabel |
Optional | string |
ra-relationships.duallistinput.availableItems |
The text or translation key to use as the label for the list of available choices |
selectedItemsLabel |
Optional | string |
ra-relationships.duallistinput.selectedItems |
The text or translation key to use as the label for the list of selected choices |
dense |
Optional | boolean |
false |
Visual density of the list component |
variant |
Optional | outlined | filled | standard |
- | Style variant for the input |
Usage
You can customize the properties to use for the option name and value, thanks to the optionText
and optionValue
attributes:
const choices = [
{ _id: 123, full_name: 'Leo Tolstoi' },
{ _id: 456, full_name: 'Jane Austen' },
];
<DualListInput
source="authors_ids"
choices={choices}
optionText="full_name"
optionValue="_id"
/>;
const choices = [
{ _id: 123, full_name: "Leo Tolstoi" },
{ _id: 456, full_name: "Jane Austen" },
];
<DualListInput source="authors_ids" choices={choices} optionText="full_name" optionValue="_id" />;
optionText
also accepts a function, so you can shape the option text at will:
const choices = [
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
<DualListInput
source="authors_ids"
choices={choices}
optionText={optionRenderer}
/>;
const choices = [
{ id: 123, first_name: "Leo", last_name: "Tolstoi" },
{ id: 456, first_name: "Jane", last_name: "Austen" },
];
const optionRenderer = (choice) => `${choice.first_name} ${choice.last_name}`;
<DualListInput source="authors_ids" choices={choices} optionText={optionRenderer} />;
optionText
also accepts a React Element, that will be cloned and receive the related choice as the record
prop. You can use Field components there.
const choices = [
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
const FullNameField = ({ record }) => (
<span>
{record.first_name} {record.last_name}
</span>
);
<DualListInput
source="authors_ids"
choices={choices}
optionText={<FullNameField />}
/>;
const choices = [
{ id: 123, first_name: "Leo", last_name: "Tolstoi" },
{ id: 456, first_name: "Jane", last_name: "Austen" },
];
const FullNameField = ({ record }) => (
<span>
{record.first_name} {record.last_name}
</span>
);
<DualListInput source="authors_ids" choices={choices} optionText={<FullNameField />} />;
The choices are translated by default, so you can use translation identifiers as choices:
const choices = [
{ id: 1, name: 'permissions.users.write' },
{ id: 2, name: 'permissions.users.read' },
{ id: 3, name: 'permissions.users.delete' },
];
const choices = [
{ id: 1, name: "permissions.users.write" },
{ id: 2, name: "permissions.users.read" },
{ id: 3, name: "permissions.users.delete" },
];
However, in some cases, you may not want the choice to be translated. In that case, set the translateChoice
prop to false
.
<DualListInput source="permissions" choices={choices} translateChoice={false} />
<DualListInput source="permissions" choices={choices} translateChoice={false} />;
Note that translateChoice
is set to false
when <DualListInput>
is a child of <ReferenceArrayInput>
.
Tip: If you want to populate the choices
attribute with a list of related records, you should decorate <DualListInput>
with <ReferenceArrayInput>
, and leave the choices
empty:
import { ReferenceArrayInput } from 'react-admin';
import { DualListInput } from '@react-admin/ra-relationships';
<ReferenceArrayInput label="Authors" source="authors_ids" reference="authors">
<DualListInput optionText="last_name" />
</ReferenceArrayInput>;
import { ReferenceArrayInput } from "react-admin";
import { DualListInput } from "@react-admin/ra-relationships";
<ReferenceArrayInput label="Authors" source="authors_ids" reference="authors">
<DualListInput optionText="last_name" />
</ReferenceArrayInput>;
You can set disabled values by setting the disabled
property of one item:
const choices = [
{ _id: 123, full_name: 'Leo Tolstoi' },
{ _id: 456, full_name: 'Jane Austen' },
{ _id: 1, full_name: 'System Administrator', disabled: true },
];
<DualListInput
source="authors_ids"
choices={choices}
optionText="full_name"
optionValue="_id"
/>;
const choices = [
{ _id: 123, full_name: "Leo Tolstoi" },
{ _id: 456, full_name: "Jane Austen" },
{ _id: 1, full_name: "System Administrator", disabled: true },
];
<DualListInput source="authors_ids" choices={choices} optionText="full_name" optionValue="_id" />;
You can use a custom field name by setting disableValue
prop:
const choices = [
{ _id: 123, full_name: 'Leo Tolstoi' },
{ _id: 456, full_name: 'Jane Austen' },
{ _id: 987, full_name: 'Jack Harden', not_available: true },
];
<DualListInput
source="contacts_ids"
choices={choices}
optionText="full_name"
optionValue="_id"
disableValue="not_available"
/>;
const choices = [
{ _id: 123, full_name: "Leo Tolstoi" },
{ _id: 456, full_name: "Jane Austen" },
{ _id: 987, full_name: "Jack Harden", not_available: true },
];
<DualListInput
source="contacts_ids"
choices={choices}
optionText="full_name"
optionValue="_id"
disableValue="not_available"
/>;
Buttons can be customized through the addButton
and removeButton
props to fit the look and feel of your application. You have 3 options:
- provide a MUI button
variant
value (outlined
,contained
ortext
) - leave it
undefined
to use the theme defaults - pass a React element. In this case, react-admin will clone the element and provide it with a
disabled
andonClick
prop.
CHANGELOG
v4.5.13
2024-06-21
- Fix
<ReferenceOneInput>
and<ReferenceManyInput>
server side errors keep the<SaveButton>
in loading state
v4.5.12
2024-06-19
- Fix
<ReferenceOneInput>
breaks server-side validation (error messages no longer appear next to the corresponding input)
Note: server-side validation on child inputs of <ReferenceOneInput>
is not yet supported.
v4.5.11
2024-05-10
- Fix
<ReferenceManyInput>
does not disable the<SaveButton>
while saving related records
v4.5.10
2024-04-03
- Export
getReferenceManyFormField
to allow users to get field name in a<ReferenceManyInput>
v4.5.9
2024-01-22
- Fix
<ReferenceManyInput>
does not applydefaultValue
in Create view with latest react-hook-form versions (>= 7.49.3) - Fix
<ReferenceOneInput>
does not applydefaultValue
in Create view with latest react-hook-form versions (>= 7.49.3)
v4.5.8
2024-01-17
- Fix
<ReferenceManyToManyInput>
does not wait for many to many operations to complete before applying the main form side effects - Update documentation to mention limitations of
undoable
mutation mode
v4.5.7
2024-01-12
- Fix
<ReferenceManyInput>
does not apply defaultValue in create view
v4.5.6
2023-11-24
- Fix
<ReferenceManyToManyInput>
ignore filter when updating the referenced records
v4.5.5
2023-10-13
- Add a story and explain how to update associative table using
<ReferenceManyInput>
v4.5.4
2023-09-18
- Fix
<ReferenceManyToManyInput>
does not use source prop
v4.5.3
2023-09-13
- Fix
<ReferenceManyToManyInput>
does not allow its children to have a default value
v4.5.2
2023-09-06
- Fix
<ReferenceOneInput>
does not applydefaultValue
in Create views
v4.5.1
2023-09-04
- Fix compatibility of
<ReferenceManyInput>
with react-hook-form v7.46.0
v4.5.0
2023-08-30
- Add support for
queryOptions
andmutationOptions
to<ReferenceOneInput>
- Add support for
queryOptions
andmutationOptions
to<ReferenceManyInput>
v4.4.0
2023-08-10
- (feat) Add
joinLimit
prop to<ReferenceManyToManyField>
to allow limit control number of results fetched from the associative table - (feat) Modify
perPage
prop in<ReferenceManyToManyField>
to allow the use of<Pagination />
v4.3.3
2023-07-05
- Fix
<ReferenceManyInput>
operations order to ensure deleted references are handled first
v4.3.2
2023-06-22
- Fix
<ReferenceManyToManyInput>
sometimes don't apply the correct values to the form
v4.3.1
2023-05-30
- Fix compatibility with latest react-hook-form versions (>= 7.43), and hence with react-admin >= v4.11
v4.3.0
2023-05-24
- Upgraded to react-admin
4.10.6
v4.2.2
2023-04-05
- (fix) Fix missing exports for
<ReferenceOneInput>
anduseReferenceOneInputController
v4.2.1
2023-03-30
- (fix)
<ReferenceManyToManyInput>
does not allow to remove the last reference when used with<DualListInput>
or<AutocompleteArrayInput>
, with React 18
v4.2.0
2023-03-23
- (feat) Add
ReferenceOneInput
- (fix)
updateManyToMany
mutation middleware should use theresource
from theResourceContext
instead of theresource
from the call to the dataProvider
v4.1.6
2023-03-20
- (fix) Fix
ReferenceManyInput
label position
v4.1.5
2023-02-03
- (fix) Fix
ReferenceManyToManyInput
default value is not set correctly
v4.1.4
2023-01-17
- (fix) Fix
ReferenceManyToManyInput
does not display values after the main record has been updated
v4.1.3
2022-11-17
- (fix) Fix
DualListInput
should not enable both buttons when user selects items in one side
v4.1.2
2022-09-26
- (fix) Fix
perPage
was not used when fetching references to compute the diff.perPageEndResource
,sortEndResource
andfilerEndResource
are now deprecated, renamed toperPageChoices
,sortChoices
andfilterChoices
v4.1.1
2022-09-09
- (fix) Fix missing export for
<ReferenceManyInput>
component
v4.1.0
2022-08-26
- Add
<ReferenceManyInput>
component
v4.0.8
2022-08-29
- (fix) Fix cropped content on
DualListInput
v4.0.7
2022-08-10
- (fix) Add
variant
support onDualListInput
- (fix) Allow custom "Add" and "Remove" buttons in
DualListInput
- (fix) Fix
<ReferenceManyToManyField>
and<ReferenceManyToManyInput>
do not set up theResourceContext
to the reference resource.
v4.0.6
2022-07-21
- (fix) Fix
useReferenceParams
cleanup of filters debounce is too aggressive
v4.0.5
2022-07-20
- (fix) Release
useReferenceParams
filters debounce on unmount
v4.0.4
2022-07-11
- (fix) Add
react-query
to the dependencies
v4.0.3
2022-07-11
- (fix) Fix
useReferenceManyToManyInputController
may return duplicates choices.
v4.0.2
2022-07-01
- (fix) Many to many updates causes old references to show up while updating
- (fix) Fixes how choices are provided to the
<ReferenceManyToMany>
input, making the<AutocompleteArrayInput>
work correctly
v4.0.1
2022-06-08
- (fix) Update peer dependencies ranges (support React 18)
v4.0.0
2022-06-07
- Upgrade to react-admin v4
- Add support for
filterEndResource
,perPageEndResource
andsortEndResource
props onReferenceManyToManyInput
anduseReferenceManyToManyInputController
. These props allow you to specify thesort
,perPage
andfilter
options applied to the queries targeting the end resource of the many-to-many relation (e.g, for a relation betweenbands
andartists
through amembers
table, these props targets the queries forartists
).
Breaking Changes
<ManyToManyReferenceContextProvider>
has been removed.
const ArtistEdit = () => (
<Edit>
- <ManyToManyReferenceContextProvider>
<SimpleForm>
<TextInput disabled source="id" />
<TextInput source="first_name" />
<TextInput source="last_name" />
<ReferenceManyToManyInput
source="id"
reference="events"
through="performances"
using="artist_id,event_id"
>
<SelectArrayInput optionText="name" />
</ReferenceManyToManyInput>
</SimpleForm>
- </ManyToManyReferenceContextProvider>
</Edit>
);
<ReferenceManyToManyInput>
is no longer responsible forlabel
,fullWidth
andvalidation
. Pass these props to its child instead.
const ArtistEdit = () => (
<Edit>
<SimpleForm>
<TextInput disabled source="id" />
<TextInput source="first_name" />
<TextInput source="last_name" />
<ReferenceManyToManyInput
source="id"
reference="events"
through="performances"
using="artist_id,event_id"
- label="Performances"
- validate={required()}
>
<SelectArrayInput
+ fullWidth
optionText="name"
+ label="Performances"
+ validate={required()}
/>
</ReferenceManyToManyInput>
</SimpleForm>
</Edit>
);
v2.1.15
2022-02-08
- (fix)
<DualListInput availableItemsLabel>
and<DualListInput selectedItemsLabel>
now work both as custom labels and translation messages
v2.1.14
2021-12-10
- (fix) ManyToManyReferenceContextProvider is not using passed basePath
v2.1.13
2021-11-22
- (fix)
<DualListInput>
Fix DualListInput throws error on double-clicking an available item if no items are selected while being child of aReferenceArrayInput
.
v2.1.12
2021-11-05
- (fix)
useReferenceManyToManyInputController
should reload the possible choices when the input value changes and one of the value is not loaded. This ensures you can leverage the quick creation support of components likeSelectArrayInput
orAutocompleteArrayInput
.
v2.1.11
2021-10-26
- (fix)
useReferenceManyToManyInputController
should not loadthrough
references when record has no id
v2.1.10
2021-07-19
- (fix) Fix
<ReferenceManyToManyField>
doesn't have a label in Show views
v2.1.9
2021-06-29
- (fix) Update peer dependencies ranges (support react 17)
v2.1.8
2021-06-01
- (doc) Fix
<DualListInput>
documentation about usage in<ReferenceArrayInput>
.
v2.1.7
2021-05-12
- (doc) Update setup instructions to includes translations
- (doc) Update DualListInput screencast
v2.1.6
2021-05-05
- (fix) Correctly handle validation on the
<ReferenceManyToManyInput>
component. Thevalidate
prop must be set on the<ReferenceManyToManyInput>
, not its children.
v2.1.5
2021-04-22
- (fix) Avoid updating main record when only the references have changed.
v2.1.4
2021-04-02
- (fix) Fix Prop types for
<ManyToManyField>
marking thesource
prop as required. - (fix) Fix README includes imports from the wrong package name.
v2.1.3
2021-03-29
- (fix) Fix documentation about limitations of the ReferenceManyToManyField and ReferenceManyToManyInput components
v2.1.2
2021-03-26
- (fix) Fix ManyToManyReferenceContextProvider Props Interface
v2.1.1
2021-03-23
- (fix) Fix ManyToManyReferenceInput props interface to include
perPage
like its controller hook.
v2.1.0
2021-02-16
- (feat) Children of ManyToManyReferenceInput are now responsible for handling the loading state.
- (feat) Add handling of LoadingState to the DualListInput
v2.0.1
2021-02-09
- (fix) ReferenceManyToManyField does not pass the perPage prop to its controller hook.
v2.0.0
2020-11-19
- (feat) Add ability to have multiple ReferenceManyToManyInput in a form.
BREAKING CHANGE
- You don't need a custom form calling either the
useReferenceManyToManyCreate
oruseReferenceManyToManyUpdate
hooks anymore. These hooks have been removed of thera-relationships
package. - Forms must now be wrapped with a
<ManyToManyReferenceContextProvider>
:
import React from 'react';
import { Edit, EditProps, SelectArrayInput, SimpleForm, TextInput } from 'react-admin';
-import { ReferenceManyToManyInput, useReferenceManyToManyUpdate } from '@react-admin/ra-many-to-many';
+import { ReferenceManyToManyInput, ManyToManyReferenceContextProvider } from '@react-admin/ra-many-to-many';
const ArtistEditForm = (props: EditProps) => {
- const save = useReferenceManyToManyUpdate({
- basePath: props.basePath,
- record: props.record,
- redirect: props.redirect || 'list',
- reference: 'events',
- resource: props.resource,
- source: 'id',
- through: 'performances',
- undoable: props.undoable,
- using: 'artist_id,event_id',
- });
-
- return <SimpleForm {...props} save={save} />;
+ return (
+ <ManyToManyReferenceContextProvider>
+ <SimpleForm {...props}
+ </ManyToManyReferenceContextProvider>
+ );
};
const ArtistEdit = (props: EditProps) => (
<Edit {...props}>
<ArtistEditForm>
<TextInput disabled source="id" />
<TextInput source="first_name" />
<TextInput source="last_name" />
<ReferenceManyToManyInput
source="id"
reference="events"
through="performances"
using="artist_id,event_id"
fullWidth
label="Performances"
>
<SelectArrayInput optionText="name" />
</ReferenceManyToManyInput>
</ArtistEditForm>
</Edit>
);
export default ArtistEdit;
Note that you don't even need a custom form anymore:
const ArtistEdit = (props: EditProps) => (
<Edit {...props}>
<ManyToManyReferenceContextProvider>
<SimpleForm>
<TextInput disabled source="id" />
<TextInput source="first_name" />
<TextInput source="last_name" />
<ReferenceManyToManyInput
source="id"
reference="events"
through="performances"
using="artist_id,event_id"
fullWidth
label="Performances"
>
<SelectArrayInput optionText="name" />
</ReferenceManyToManyInput>
</SimpleForm>
</ManyToManyReferenceContextProvider>
</Edit>
);
const ArtistEdit = (props) => (
<Edit {...props}>
<ManyToManyReferenceContextProvider>
<SimpleForm>
<TextInput disabled source="id" />
<TextInput source="first_name" />
<TextInput source="last_name" />
<ReferenceManyToManyInput
source="id"
reference="events"
through="performances"
using="artist_id,event_id"
fullWidth
label="Performances"
>
<SelectArrayInput optionText="name" />
</ReferenceManyToManyInput>
</SimpleForm>
</ManyToManyReferenceContextProvider>
</Edit>
);
v1.2.1
2020-12-08
- (fix) Fix ManyToManyInput does not fetch the correct references
v1.2.0
2020-10-12
- (fix) Update DualListInput button labels (select/unselect instead of add/remove)
- (fix) Disable buttons when no item is selected
v1.1.0
2020-10-05
- Upgrade to react-admin
3.9
v1.0.0
2020-09-15
- First release