ra-form-layout
New form layouts for complex data entry tasks (accordion, wizard, etc.).
Test it live on the Enterprise Edition Storybook and in the e-commerce demo (Accordion Form, WizardForm).
Installation
npm install --save @react-admin/ra-form-layout
# or
yarn add @react-admin/ra-form-layout
Tip: ra-form-layout
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 {
raFormLayoutLanguageEnglish,
raFormLayoutLanguageFrench,
} from '@react-admin/ra-form-layout';
const messages = {
en: { ...englishMessages, ...raFormLayoutLanguageEnglish },
fr: { ...frenchMessages, ...raFormLayoutLanguageFrench },
};
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 { raFormLayoutLanguageEnglish, raFormLayoutLanguageFrench } from "@react-admin/ra-form-layout";
const messages = {
en: { ...englishMessages, ...raFormLayoutLanguageEnglish },
fr: { ...frenchMessages, ...raFormLayoutLanguageFrench },
};
const i18nProvider = polyglotI18nProvider((locale) => messages[locale], "en");
const App = () => <Admin i18nProvider={is18nProvider}>{/* ... */}</Admin>;
<AccordionForm>
Alternative to <SimpleForm>
, to be used as child of <Create>
or <Edit>
. Expects <AccordionForm.Panel>
elements as children.
Test it live in the e-commerce demo.
By default, each child accordion element handles its expanded state independently.
import {
Edit,
TextField,
TextInput,
DateInput,
SelectInput,
ArrayInput,
SimpleFormIterator,
BooleanInput,
} from 'react-admin';
import { AccordionForm, AccordionForm.Panel } from '@react-admin/ra-form-layout';
// don't forget the component="div" prop on the main component to disable the main Card
const CustomerEdit = () => (
<Edit component="div">
<AccordionForm autoClose>
<AccordionForm.Panel label="Identity">
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</AccordionForm.Panel>
<AccordionForm.Panel label="Occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionForm.Panel>
<AccordionForm.Panel label="Preferences">
<SelectInput
source="language"
choices={languageChoices}
defaultValue="en"
/>
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
import {
Edit,
TextField,
TextInput,
DateInput,
SelectInput,
ArrayInput,
SimpleFormIterator,
BooleanInput,
} from "react-admin";
import { AccordionForm } from "@react-admin/ra-form-layout";
// don't forget the component="div" prop on the main component to disable the main Card
const CustomerEdit = () => (
<Edit component="div">
<AccordionForm autoClose>
<AccordionForm.Panel label="Identity">
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</AccordionForm.Panel>
<AccordionForm.Panel label="Occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionForm.Panel>
<AccordionForm.Panel label="Preferences">
<SelectInput source="language" choices={languageChoices} defaultValue="en" />
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
Props
This component accepts the following props in addition to the <Form>
props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
authorizationError |
Optional | ReactNode |
null |
The content to display when authorization checks fail |
autoClose |
Optional | boolean |
false |
Enable auto-closing currently opened panel when another is opened |
enableAccessControl |
Optional | boolean |
false |
Enable checking authorization rights for each panel and input |
loading |
Optional | ReactNode |
The content to display when checking authorizations | |
toolbar |
Optional | ReactNode |
The toolbar element | |
sx |
Optional | object |
Custom styles |
authorizationError
Content displayed when enableAccessControl
is set to true
and an error occurs while checking for users permissions. Defaults to null
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { AccordionForm } from '@react-admin/ra-form-layout';
import { Alert } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<AccordionForm
enableAccessControl
authorizationError={
<Alert
severity="error"
sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}
>
An error occurred while loading your permissions
</Alert>
}
>
<AccordionForm.Panel id="identity" defaultExpanded>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</AccordionForm.Panel>
<AccordionForm.Panel id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { AccordionForm } from "@react-admin/ra-form-layout";
import { Alert } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<AccordionForm
enableAccessControl
authorizationError={
<Alert severity="error" sx={{ px: 2.5, py: 1, mt: 1, width: "100%" }}>
An error occurred while loading your permissions
</Alert>
}
>
<AccordionForm.Panel id="identity" defaultExpanded>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</AccordionForm.Panel>
<AccordionForm.Panel id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
autoClose
When setting autoClose
in the <AccordionForm>
, only one accordion remains open at a time. The first accordion is open by default, and when a user opens another one, the current open accordion closes.
import { Edit, TextField, TextInput, DateInput, SelectInput, ArrayInput, SimpleFormIterator, BooleanInput } from 'react-admin';
import { AccordionForm, AccordionForm.Panel } from '@react-admin/ra-form-layout';
// don't forget the component="div" prop on the main component to disable the main Card
const CustomerEdit = (props: EditProps) => (
<Edit {...props} component="div">
- <AccordionForm>
+ <AccordionForm autoClose>
<AccordionForm.Panel label="Identity" defaultExpanded>
<TextField source="id" />
...
enableAccessControl
When set to true
, React-admin will call the authProvider.canAccess
method for each panel with the following parameters:
action
:write
resource
:RESOURCE_NAME.panel.PANEL_ID_OR_LABEL
. For instance:customers.panel.identity
record
: The current record
For each panel, react-admin will also call the authProvider.canAccess
method for each input with the following parameters:
action
:write
resource
:RESOURCE_NAME.INPUT_SOURCE
. For instance:customers.first_name
record
: The current record
Tip: <AccordionForm.Panel>
direct children that don't have a source
will always be displayed.
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { AccordionForm } from '@react-admin/ra-form-layout';
const CustomerEdit = () => (
<Edit>
<AccordionForm enableAccessControl>
<AccordionForm.Panel id="identity" defaultExpanded>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</AccordionForm.Panel>
<AccordionForm.Panel id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { AccordionForm } from "@react-admin/ra-form-layout";
const CustomerEdit = () => (
<Edit>
<AccordionForm enableAccessControl>
<AccordionForm.Panel id="identity" defaultExpanded>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</AccordionForm.Panel>
<AccordionForm.Panel id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
Tip: If you only want access control for the panels but not for the inputs, set the enableAccessControl
prop to false
on the <AccordionForm.Panel>
.
loading
Content displayed when enableAccessControl
is set to true
while checking for users permissions. Defaults to Loading
from react-admin
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { AccordionForm } from '@react-admin/ra-form-layout';
import { Typography } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<AccordionForm
enableAccessControl
loading={
<Typography>
Loading your permissions...
</Typography>
}
>
<AccordionForm.Panel id="identity" defaultExpanded>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</AccordionForm.Panel>
<AccordionForm.Panel id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { AccordionForm } from "@react-admin/ra-form-layout";
import { Typography } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<AccordionForm enableAccessControl loading={<Typography>Loading your permissions...</Typography>}>
<AccordionForm.Panel id="identity" defaultExpanded>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</AccordionForm.Panel>
<AccordionForm.Panel id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
toolbar
You can customize the form Toolbar by passing a custom element in the toolbar
prop. The form expects the same type of element as <SimpleForm>
, see the <SimpleForm toolbar>
prop documentation in the react-admin docs.
import { Edit, SaveButton, Toolbar as RaToolbar } from 'react-admin';
import { AccordionForm } from '@react-admin/ra-form-layout';
const CustomerCustomToolbar = props => (
<RaToolbar {...props}>
<SaveButton label="Save and return" type="button" variant="outlined" />
</RaToolbar>
);
const CustomerEditWithToolbar = () => (
<Edit component="div">
<AccordionForm toolbar={<CustomerCustomToolbar />}>
<AccordionForm.Panel label="Identity">{/* ... */}</AccordionForm.Panel>
<AccordionForm.Panel label="Occupations">{/* ... */}</AccordionForm.Panel>
<AccordionForm.Panel label="Preferences">{/* ... */}</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
import { Edit, SaveButton, Toolbar as RaToolbar } from "react-admin";
import { AccordionForm } from "@react-admin/ra-form-layout";
const CustomerCustomToolbar = (props) => (
<RaToolbar {...props}>
<SaveButton label="Save and return" type="button" variant="outlined" />
</RaToolbar>
);
const CustomerEditWithToolbar = () => (
<Edit component="div">
<AccordionForm toolbar={<CustomerCustomToolbar />}>
<AccordionForm.Panel label="Identity">{/* ... */}</AccordionForm.Panel>
<AccordionForm.Panel label="Occupations">{/* ... */}</AccordionForm.Panel>
<AccordionForm.Panel label="Preferences">{/* ... */}</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
sx
: CSS API
The <AccordionForm>
component accepts the usual className
prop. You can also override the styles of the inner components thanks to the sx
property. This property accepts the following subclasses:
Rule name | Description |
---|---|
&.MuiBox-root |
Applied to the root component |
&.MuiAccordion-root |
Applied to all the Accordions |
&.Mui-expanded |
Applied to the expanded Accordions |
&.MuiAccordionSummary-root |
Applied to the Accordion's title |
&.MuiCollapse-root |
Applied to the Accordion's content |
<AccordionForm.Panel>
The children of <AccordionForm>
must be <AccordionForm.Panel>
elements.
This component renders a MUI <Accordion>
component. In the <AccordionDetails>
, renders each child inside a <FormInput>
(the same layout as in <SimpleForm>
). |
import {
Edit,
TextField,
TextInput,
DateInput,
SelectInput,
ArrayInput,
SimpleFormIterator,
BooleanInput,
} from 'react-admin';
import { AccordionForm } from '@react-admin/ra-form-layout';
const CustomerEdit = () => (
<Edit component="div">
<AccordionForm>
<AccordionForm.Panel label="Identity" defaultExpanded>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
import { Edit, TextField, TextInput, DateInput, SelectInput } from "react-admin";
import { AccordionForm } from "@react-admin/ra-form-layout";
const CustomerEdit = () => (
<Edit component="div">
<AccordionForm>
<AccordionForm.Panel label="Identity" defaultExpanded>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</AccordionForm.Panel>
</AccordionForm>
</Edit>
);
Warning: To use an <AccordionForm.Panel>
with the autoClose
prop and a React node element as a label
, you must specify an id
.
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
children |
Required | ReactNode |
- | A list of <Input> elements |
defaultExpanded |
Optional | boolean |
false |
Set to true to have the accordion expanded by default (except if autoClose = true on the parent) |
disabled |
Optional | boolean |
false |
If true, the accordion will be displayed in a disabled state. |
count |
Optional | ReactNode |
- | A number to be displayed next to the summary, to quantify it |
enableAccessControl |
Optional | boolean |
false |
Enable checking authorization rights for each panel and input |
id |
Optional | string |
- | An id for this Accordion to be used in the useFormGroup hook and for CSS classes. |
label |
Required | string or ReactNode |
- | The main label used as the accordion summary. Appears in red when the accordion has errors |
secondary |
Optional | string or ReactNode |
- | The secondary label used as the accordion summary |
square |
Optional | boolean |
false |
If true, rounded corners are disabled. |
sx |
Optional | Object |
- | An object containing the MUI style overrides to apply to the root component. |
<AccordionSection>
Renders children (Inputs) inside a MUI <Accordion>
element without a Card style. To be used as child of a <SimpleForm>
or a <TabbedForm>
element.
Prefer <AccordionSection>
to <AccordionForm>
to always display a list of important inputs, then offer accordions for secondary inputs.
Props
Prop | Required | Type | Default | Description |
---|---|---|---|---|
authorizationError |
Optional | Component |
- | The component to use when an error occurs while checking permissions. |
Accordion |
Optional | Component |
- | The component to use as the accordion. |
AccordionDetails |
Optional | Component |
- | The component to use as the accordion details. |
AccordionSummary |
Optional | Component |
- | The component to use as the accordion summary. |
count |
Optional | ReactNode |
- | A number to be displayed next to the summary, to quantify it |
enableAccessControl |
Optional | boolean |
- | Enable access control to the section and its inputs |
label |
Required | string or ReactNode |
- | The main label used as the accordion summary. |
loading |
Optional | Component |
- | The component to use while checking permissions. |
id |
Optional | string |
- | An id for this Accordion to be used for CSS classes. |
children |
Required | ReactNode |
- | A list of <Input> elements |
fullWidth |
Optional | boolean |
- | Set to false to disable the Accordion taking the entire form width. |
className |
Optional | string |
- | A class name to style the underlying <Accordion> |
secondary |
Optional | string or ReactNode |
- | The secondary label used as the accordion summary |
defaultExpanded |
Optional | boolean |
false |
Set to true to have the accordion expanded by default |
disabled |
Optional | boolean |
false |
If true, the accordion will be displayed in a disabled state. |
square |
Optional | boolean |
false |
If true, rounded corners are disabled. |
import {
Edit,
TextField,
TextInput,
DateInput,
SelectInput,
ArrayInput,
SimpleForm,
SimpleFormIterator,
BooleanInput,
} from 'react-admin';
import { AccordionForm, AccordionForm.Panel } from '@react-admin/ra-form-layout';
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
<AccordionSection label="Occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionSection>
<AccordionSection label="Preferences">
<SelectInput
source="language"
choices={languageChoices}
defaultValue="en"
/>
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
import {
Edit,
TextField,
TextInput,
DateInput,
SelectInput,
ArrayInput,
SimpleForm,
SimpleFormIterator,
BooleanInput,
} from "react-admin";
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
<AccordionSection label="Occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</AccordionSection>
<AccordionSection label="Preferences">
<SelectInput source="language" choices={languageChoices} defaultValue="en" />
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
accessDenied
Content displayed when enableAccessControl
is set to true
and users don't have access to the section. Defaults to null
:
import { ReactNode } from 'react';
import { ArrayInput, BooleanInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { AccordionSection } from '@react-admin/ra-form-layout';
import { Alert } from '@mui/material';
const AccessDenied = ({ children }: { children: ReactNode }) => (
<Alert
severity="info"
sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}
action={
<Button color="inherit" size="small">
Upgrade to Premium
</Button>
}
>
{children}
</Alert>
)
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<AccordionSection
label="Preferences"
enableAccessControl
accessDenied={<AccessDenied>You don't have access to the preferences section</AccessDenied>}
>
<SelectInput
source="language"
choices={languageChoices}
defaultValue="en"
/>
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
import { BooleanInput, Edit, TextInput } from "react-admin";
import { AccordionSection } from "@react-admin/ra-form-layout";
import { Alert } from "@mui/material";
const AccessDenied = ({ children }) => (
<Alert
severity="info"
sx={{ px: 2.5, py: 1, mt: 1, width: "100%" }}
action={
<Button color="inherit" size="small">
Upgrade to Premium
</Button>
}
>
{children}
</Alert>
);
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<AccordionSection
label="Preferences"
enableAccessControl
accessDenied={<AccessDenied>You don't have access to the preferences section</AccessDenied>}
>
<SelectInput source="language" choices={languageChoices} defaultValue="en" />
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
authorizationError
Content displayed when enableAccessControl
is set to true
and an error occurs while checking for users permissions. Defaults to null
:
import { ArrayInput, BooleanInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { AccordionSection } from '@react-admin/ra-form-layout';
import { Alert } from '@mui/material';
const AuthorizationError = () => (
<Alert
severity="error"
sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}
>
An error occurred while loading your permissions
</Alert>
);
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<AccordionSection label="Preferences" enableAccessControl authorizationError={<AuthorizationError />}>
<SelectInput
source="language"
choices={languageChoices}
defaultValue="en"
/>
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
import { BooleanInput, Edit, TextInput } from "react-admin";
import { AccordionSection } from "@react-admin/ra-form-layout";
import { Alert } from "@mui/material";
const AuthorizationError = () => (
<Alert severity="error" sx={{ px: 2.5, py: 1, mt: 1, width: "100%" }}>
An error occurred while loading your permissions
</Alert>
);
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<AccordionSection label="Preferences" enableAccessControl authorizationError={<AuthorizationError />}>
<SelectInput source="language" choices={languageChoices} defaultValue="en" />
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
enableAccessControl
When set to true
, React-admin will call the authProvider.canAccess
method the following parameters:
action
:write
resource
:RESOURCE_NAME.section.PANEL_ID_OR_LABEL
. For instance:customers.section.identity
record
: The current record
React-admin will also call the authProvider.canAccess
method for each input with the following parameters:
action
:write
resource
:RESOURCE_NAME.INPUT_SOURCE
. For instance:customers.first_name
record
: The current record
import { ArrayInput, BooleanInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { AccordionSection } from '@react-admin/ra-form-layout';
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
<AccordionSection label="Preferences" enableAccessControl>
<SelectInput
source="language"
choices={languageChoices}
defaultValue="en"
/>
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
import { BooleanInput, Edit, DateInput, TextInput } from "react-admin";
import { AccordionSection } from "@react-admin/ra-form-layout";
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
<AccordionSection label="Preferences" enableAccessControl>
<SelectInput source="language" choices={languageChoices} defaultValue="en" />
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
Tip: <AccordionSection>
direct children that don't have a source
will always be displayed.
loading
Content displayed when enableAccessControl
is set to true
while checking for users permissions. Defaults to Loading
from react-admin
:
import { ArrayInput, BooleanInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { AccordionSection } from '@react-admin/ra-form-layout';
import { Typography } from '@mui/material';
const AuthorizationLoading = () => (
<Typography>
Loading your permissions...
</Typography>
);
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
<AccordionSection label="Preferences" enableAccessControl loading={<AuthorizationLoading />}>
<SelectInput
source="language"
choices={languageChoices}
defaultValue="en"
/>
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
import { BooleanInput, Edit, DateInput, TextInput } from "react-admin";
import { AccordionSection } from "@react-admin/ra-form-layout";
import { Typography } from "@mui/material";
const AuthorizationLoading = () => <Typography>Loading your permissions...</Typography>;
const CustomerEdit = () => (
<Edit component="div">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
<AccordionSection label="Preferences" enableAccessControl loading={<AuthorizationLoading />}>
<SelectInput source="language" choices={languageChoices} defaultValue="en" />
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</AccordionSection>
</SimpleForm>
</Edit>
);
<WizardForm>
Alternative to <SimpleForm>
that splits a form into a step-by-step interface, to facilitate the entry in long forms.
Test it live in the e-commerce demo.
Use <WizardForm>
as the child of <Create>
. It expects <WizardFormStep>
elements as children.
import React from 'react';
import { Create, TextInput, required } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
const PostCreate = () => (
<Create>
<WizardForm>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
import React from "react";
import { Create, TextInput, required } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
const PostCreate = () => (
<Create>
<WizardForm>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
Note: You can also use the <WizardForm>
as child of <Edit>
but it's considered as a bad practice to provide a wizard form for existing resources.
Tip: The label
prop of the <WizardForm.Step>
component accepts a translation key:
import React from 'react';
import { Create, TextInput, required } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
const PostCreate = () => (
<Create>
<WizardForm>
<WizardForm.Step label="myapp.posts.steps.general">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="myapp.posts.steps.description">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="myapp.posts.steps.misc">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
import React from "react";
import { Create, TextInput, required } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
const PostCreate = () => (
<Create>
<WizardForm>
<WizardForm.Step label="myapp.posts.steps.general">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="myapp.posts.steps.description">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="myapp.posts.steps.misc">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
Props
This component accepts the following props in addition to the <Form>
props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
authorizationError |
Optional | ReactNode |
null |
The content to display when authorization checks fail |
enableAccessControl |
Optional | boolean |
false |
Enable checking authorization rights for each panel and input |
loading |
Optional | ReactNode |
The content to display when checking authorizations | |
progress |
Optional | ReactNode |
The progress element | |
toolbar |
Optional | ReactNode |
The toolbar element |
authorizationError
Used when enableAccessControl
is set to true
and an error occurs while checking for users permissions. Defaults to null
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
import { Alert } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<WizardForm
enableAccessControl
authorizationError={
<Alert
severity="error"
sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}
>
An error occurred while loading your permissions
</Alert>
}
>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
import { Alert } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<WizardForm
enableAccessControl
authorizationError={
<Alert severity="error" sx={{ px: 2.5, py: 1, mt: 1, width: "100%" }}>
An error occurred while loading your permissions
</Alert>
}
>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
enableAccessControl
When set to true
, React-admin will call the authProvider.canAccess
method for each panel with the following parameters:
action
:write
resource
:RESOURCE_NAME.section.PANEL_ID_OR_LABEL
. For instance:customers.section.identity
record
: The current record
For each panel, react-admin will also call the authProvider.canAccess
method for each input with the following parameters:
action
:write
resource
:RESOURCE_NAME.INPUT_SOURCE
. For instance:customers.first_name
record
: The current record
Tip: <WizardForm.Step>
direct children that don't have a source
will always be displayed.
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
const CustomerEdit = () => (
<Edit>
<WizardForm enableAccessControl>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
const CustomerEdit = () => (
<Edit>
<WizardForm enableAccessControl>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
loading
Used when enableAccessControl
is set to true
while checking for users permissions. Defaults to Loading
from react-admin
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
import { Typography } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<WizardForm
enableAccessControl
loading={
<Typography>
Loading your permissions...
</Typography>
}
>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
import { Typography } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<WizardForm enableAccessControl loading={<Typography>Loading your permissions...</Typography>}>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
progress
You can also customize the progress stepper by passing a custom component in the progress
prop.
import React from 'react';
import { Create, TextInput, required } from 'react-admin';
import {
WizardForm,
WizardFormProgressProps,
useWizardFormContext
} from '@react-admin/ra-form-layout';
const MyProgress = (props: WizardFormProgressProps) => {
const { currentStep, steps } = useWizardFormContext(props);
return (
<ul>
{steps.map((step, index) => {
const label = React.cloneElement(step, { intent: 'label' });
return (
<li key={`step_${index}`}>
<span
style={{
textDecoration:
currentStep === index
? 'underline'
: undefined,
}}
>
{label}
</span>
</li>
);
})}
</ul>
);
};
const PostCreate = () => (
<Create>
<WizardForm progress={<MyProgress />}>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
import React from "react";
import { Create, TextInput, required } from "react-admin";
import { WizardForm, useWizardFormContext } from "@react-admin/ra-form-layout";
const MyProgress = (props) => {
const { currentStep, steps } = useWizardFormContext(props);
return (
<ul>
{steps.map((step, index) => {
const label = React.cloneElement(step, { intent: "label" });
return (
<li key={`step_${index}`}>
<span
style={{
textDecoration: currentStep === index ? "underline" : undefined,
}}
>
{label}
</span>
</li>
);
})}
</ul>
);
};
const PostCreate = () => (
<Create>
<WizardForm progress={<MyProgress />}>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
Any additional props will be passed to the <Progress>
component.
You can also hide the progress stepper completely by setting progress
to false
.
import React from 'react';
import { Create, TextInput, required } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
const PostCreate = () => (
<Create>
<WizardForm progress={false}>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
import React from "react";
import { Create, TextInput, required } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
const PostCreate = () => (
<Create>
<WizardForm progress={false}>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
toolbar
You can customize the form toolbar by passing a custom component in the toolbar
prop.
import { Button } from '@mui/material';
import React from 'react';
import { Create, required, TextInput, useSaveContext } from 'react-admin';
import { useFormState } from 'react-hook-form';
import { useWizardFormContext, WizardForm } from '@react-admin/ra-form-layout';
const MyToolbar = () => {
const { hasNextStep, hasPreviousStep, goToNextStep, goToPreviousStep } =
useWizardFormContext();
const { save } = useSaveContext();
const { isValidating } = useFormState();
return (
<ul>
{hasPreviousStep ? (
<li>
<Button onClick={() => goToPreviousStep()}>PREVIOUS</Button>
</li>
) : null}
{hasNextStep ? (
<li>
<Button
disabled={isValidating}
onClick={() => goToNextStep()}
>
NEXT
</Button>
</li>
) : (
<li>
<Button disabled={isValidating} onClick={save}>
SAVE
</Button>
</li>
)}
</ul>
);
};
const PostCreate = () => (
<Create>
<WizardForm toolbar={<MyToolbar />}>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
import { Button } from "@mui/material";
import React from "react";
import { Create, required, TextInput, useSaveContext } from "react-admin";
import { useFormState } from "react-hook-form";
import { useWizardFormContext, WizardForm } from "@react-admin/ra-form-layout";
const MyToolbar = () => {
const { hasNextStep, hasPreviousStep, goToNextStep, goToPreviousStep } = useWizardFormContext();
const { save } = useSaveContext();
const { isValidating } = useFormState();
return (
<ul>
{hasPreviousStep ? (
<li>
<Button onClick={() => goToPreviousStep()}>PREVIOUS</Button>
</li>
) : null}
{hasNextStep ? (
<li>
<Button disabled={isValidating} onClick={() => goToNextStep()}>
NEXT
</Button>
</li>
) : (
<li>
<Button disabled={isValidating} onClick={save}>
SAVE
</Button>
</li>
)}
</ul>
);
};
const PostCreate = () => (
<Create>
<WizardForm toolbar={<MyToolbar />}>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
</WizardForm>
</Create>
);
Adding a Summary Final Step
In order to add a final step with a summary of the form values before submit, you can leverage react-hook-form
useWatch
hook:
const FinalStepContent = () => {
const values = useWatch({
name: ['title', 'description', 'fullDescription'],
});
return values?.length > 0 ? (
<>
<Typography>title: {values[0]}</Typography>
<Typography>description: {values[1]}</Typography>
<Typography>fullDescription: {values[2]}</Typography>
</>
) : null;
};
const PostCreate = () => (
<Create>
<WizardForm>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="">
<FinalStepContent />
</WizardForm.Step>
</WizardForm>
</Create>
);
const FinalStepContent = () => {
const values = useWatch({
name: ["title", "description", "fullDescription"],
});
return values?.length > 0 ? (
<>
<Typography>title: {values[0]}</Typography>
<Typography>description: {values[1]}</Typography>
<Typography>fullDescription: {values[2]}</Typography>
</>
) : null;
};
const PostCreate = () => (
<Create>
<WizardForm>
<WizardForm.Step label="First step">
<TextInput source="title" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="Second step">
<TextInput source="description" />
</WizardForm.Step>
<WizardForm.Step label="Third step">
<TextInput source="fullDescription" validate={required()} />
</WizardForm.Step>
<WizardForm.Step label="">
<FinalStepContent />
</WizardForm.Step>
</WizardForm>
</Create>
);
<WizardForm.Step>
The children of <WizardForm>
must be <WizardForm.Step>
elements.
It accepts the following props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
authorizationError |
Optional | ReactNode |
- | The content to display when authorization checks fail |
enableAccessControl |
Optional | ReactNode |
- | Enable authorization checks |
label |
Required | string |
- | The main label used as the step title. Appears in red when the section has errors |
loading |
Optional | ReactNode |
- | The content to display while checking authorizations |
children |
Required | ReactNode |
- | A list of <Input> elements |
sx |
Optional | object |
- | An object containing the MUI style overrides to apply to the root component |
authorizationError
Used when enableAccessControl
is set to true
and an error occurs while checking for users permissions. Defaults to null
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
import { Alert } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<WizardForm enableAccessControl>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations" authorizationError={
<Alert
severity="error"
sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}
>
An error occurred while loading your permissions
</Alert>
}>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
import { Alert } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<WizardForm enableAccessControl>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step
id="occupations"
authorizationError={
<Alert severity="error" sx={{ px: 2.5, py: 1, mt: 1, width: "100%" }}>
An error occurred while loading your permissions
</Alert>
}
>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
enableAccessControl
When set to true
, react-admin will also call the authProvider.canAccess
method for each input with the following parameters:
action
:write
resource
:RESOURCE_NAME.INPUT_SOURCE
. For instance:customers.first_name
record
: The current record
Tip: <WizardForm.Step>
direct children that don't have a source
will always be displayed.
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
const CustomerEdit = () => (
<Edit>
<WizardForm>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations" enableAccessControl>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
const CustomerEdit = () => (
<Edit>
<WizardForm>
<WizardForm.Step id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations" enableAccessControl>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
loading
Used when enableAccessControl
is set to true
while checking for users permissions. Defaults to Loading
from react-admin
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { WizardForm } from '@react-admin/ra-form-layout';
import { Typography } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<WizardForm enableAccessControl>
<WizardForm.Step id="identity" loading={
<Typography>
Loading your permissions...
</Typography>
}>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations" loading={
<Typography>
Loading your permissions...
</Typography>
}>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { WizardForm } from "@react-admin/ra-form-layout";
import { Typography } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<WizardForm enableAccessControl>
<WizardForm.Step id="identity" loading={<Typography>Loading your permissions...</Typography>}>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</WizardForm.Step>
<WizardForm.Step id="occupations" loading={<Typography>Loading your permissions...</Typography>}>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</WizardForm.Step>
</WizardForm>
</Edit>
);
<LongForm>
Alternative to <SimpleForm>
, to be used as child of <Create>
or <Edit>
. Expects <LongForm.Section>
elements as children.
Test it live on the Enterprise Edition Storybook.
This component will come in handy if you need to create a long form, with many input fields divided into several sections. It makes navigation easier, by providing a TOC (Table Of Contents) and by keeping the toolbar fixed at the bottom position.
import {
ArrayInput,
BooleanInput,
DateInput,
Edit,
required,
SelectInput,
SimpleFormIterator,
TextField,
TextInput,
Labeled,
} from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
const sexChoices = [
{ id: 'male', name: 'Male' },
{ id: 'female', name: 'Female' },
];
const languageChoices = [
{ id: 'en', name: 'English' },
{ id: 'fr', name: 'French' },
];
const CustomerEdit = () => (
<Edit component="div">
<LongForm>
<LongForm.Section label="Identity">
<Labeled label="id">
<TextField source="id" />
</Labeled>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</LongForm.Section>
<LongForm.Section label="Occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
<LongForm.Section label="Preferences">
<SelectInput
source="language"
choices={languageChoices}
defaultValue="en"
/>
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</LongForm.Section>
</LongForm>
</Edit>
);
import {
ArrayInput,
BooleanInput,
DateInput,
Edit,
required,
SelectInput,
SimpleFormIterator,
TextField,
TextInput,
Labeled,
} from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
const sexChoices = [
{ id: "male", name: "Male" },
{ id: "female", name: "Female" },
];
const languageChoices = [
{ id: "en", name: "English" },
{ id: "fr", name: "French" },
];
const CustomerEdit = () => (
<Edit component="div">
<LongForm>
<LongForm.Section label="Identity">
<Labeled label="id">
<TextField source="id" />
</Labeled>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</LongForm.Section>
<LongForm.Section label="Occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
<LongForm.Section label="Preferences">
<SelectInput source="language" choices={languageChoices} defaultValue="en" />
<BooleanInput source="dark_theme" />
<BooleanInput source="accepts_emails_from_partners" />
</LongForm.Section>
</LongForm>
</Edit>
);
Props
This component accepts the following props in addition to the <Form>
props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
authorizationError |
Optional | ReactNode |
null |
The content to display when authorization checks fail |
enableAccessControl |
Optional | boolean |
false |
Enable checking authorization rights for each section and input |
loading |
Optional | ReactNode |
The content to display when checking authorizations | |
toolbar |
Optional | ReactNode |
The toolbar element | |
sx |
Optional | object |
Custom styles |
authorizationError
Used when enableAccessControl
is set to true
and an error occurs while checking for users permissions. Defaults to null
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
import { Alert } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<LongForm
enableAccessControl
authorizationError={
<Alert
severity="error"
sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}
>
An error occurred while loading your permissions
</Alert>
}
>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
import { Alert } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<LongForm
enableAccessControl
authorizationError={
<Alert severity="error" sx={{ px: 2.5, py: 1, mt: 1, width: "100%" }}>
An error occurred while loading your permissions
</Alert>
}
>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
enableAccessControl
When set to true
, React-admin will call the authProvider.canAccess
method for each section with the following parameters:
action
:write
resource
:RESOURCE_NAME.section.SECTION_ID_OR_LABEL
. For instance:customers.section.identity
record
: The current record
For each section, react-admin will also call the authProvider.canAccess
method for each input with the following parameters:
action
:write
resource
:RESOURCE_NAME.INPUT_SOURCE
. For instance:customers.first_name
record
: The current record
Tip: <LongForm.Section>
direct children that don't have a source
will always be displayed.
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
const CustomerEdit = () => (
<Edit>
<LongForm enableAccessControl>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
const CustomerEdit = () => (
<Edit>
<LongForm enableAccessControl>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
loading
Used when enableAccessControl
is set to true
while checking for users permissions. Defaults to Loading
from react-admin
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
import { Typography } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<LongForm
enableAccessControl
loading={
<Typography>
Loading your permissions...
</Typography>
}
>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
import { Typography } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<LongForm enableAccessControl loading={<Typography>Loading your permissions...</Typography>}>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations">
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
toolbar
You can customize the form Toolbar by passing a custom element in the toolbar
prop. The form expects the same type of element as <SimpleForm>
, see the <SimpleForm toolbar>
prop documentation in the react-admin docs.
import { Edit, SaveButton, Toolbar as RaToolbar } from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
const CustomerCustomToolbar = props => (
<RaToolbar {...props}>
<SaveButton label="Save and return" type="button" variant="outlined" />
</RaToolbar>
);
const CustomerEditWithToolbar = () => (
<Edit component="div">
<LongForm toolbar={<CustomerCustomToolbar />}>
<LongForm.Section label="Identity">...</LongForm.Section>
<LongForm.Section label="Occupations">...</LongForm.Section>
<LongForm.Section label="Preferences">...</LongForm.Section>
</LongForm>
</Edit>
);
import { Edit, SaveButton, Toolbar as RaToolbar } from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
const CustomerCustomToolbar = (props) => (
<RaToolbar {...props}>
<SaveButton label="Save and return" type="button" variant="outlined" />
</RaToolbar>
);
const CustomerEditWithToolbar = () => (
<Edit component="div">
<LongForm toolbar={<CustomerCustomToolbar />}>
<LongForm.Section label="Identity">...</LongForm.Section>
<LongForm.Section label="Occupations">...</LongForm.Section>
<LongForm.Section label="Preferences">...</LongForm.Section>
</LongForm>
</Edit>
);
sx
: CSS API
The <LongForm>
component accepts the usual className
prop. You can also override the styles of the inner components thanks to the sx
property. This property accepts the following subclasses:
Rule name | Description |
---|---|
&.RaLongForm-root |
Applied to the root component |
& .RaLongForm-toc |
Applied to the TOC |
& .RaLongForm-main |
Applied to the main <Card> component |
& .RaLongForm-toolbar |
Applied to the toolbar |
& .RaLongForm-error |
Applied to the <MenuItem> in case the section has validation errors |
<LongForm.Section>
The children of <LongForm>
must be <LongForm.Section>
elements.
This component adds a section title (using a <Typography variant="h4">
), then renders each child inside a MUI <Stack>
, and finally adds an MUI <Divider>
at the bottom of the section.
It accepts the following props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
authorizationError |
Optional | ReactNode |
- | The content to display when authorization checks fail |
enableAccessControl |
Optional | ReactNode |
- | Enable authorization checks |
label |
Required | string |
- | The main label used as the section title. Appears in red when the section has errors |
loading |
Optional | ReactNode |
- | The content to display while checking authorizations |
children |
Required | ReactNode |
- | A list of <Input> elements |
cardinality |
Optional | number |
- | A number to be displayed next to the label in TOC, to quantify it |
sx |
Optional | object |
- | An object containing the MUI style overrides to apply to the root component |
authorizationError
Used when enableAccessControl
is set to true
and an error occurs while checking for users permissions. Defaults to null
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
import { Alert } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<LongForm enableAccessControl>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations">
<ArrayInput source="occupations" label="" authorizationError={
<Alert
severity="error"
sx={{ px: 2.5, py: 1, mt: 1, width: '100%' }}
>
An error occurred while loading your permissions
</Alert>
}>
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
import { Alert } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<LongForm enableAccessControl>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations">
<ArrayInput
source="occupations"
label=""
authorizationError={
<Alert severity="error" sx={{ px: 2.5, py: 1, mt: 1, width: "100%" }}>
An error occurred while loading your permissions
</Alert>
}
>
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
enableAccessControl
When set to true
, react-admin will also call the authProvider.canAccess
method for each input with the following parameters:
action
:write
resource
:RESOURCE_NAME.INPUT_SOURCE
. For instance:customers.first_name
record
: The current record
Tip: <LongForm.Section>
direct children that don't have a source
will always be displayed.
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
const CustomerEdit = () => (
<Edit>
<LongForm>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations" enableAccessControl>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
const CustomerEdit = () => (
<Edit>
<LongForm>
<LongForm.Section id="identity">
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations" enableAccessControl>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
loading
Used when enableAccessControl
is set to true
while checking for users permissions. Defaults to Loading
from react-admin
:
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
import { Typography } from '@mui/material';
const CustomerEdit = () => (
<Edit>
<LongForm enableAccessControl>
<LongForm.Section id="identity" loading={
<Typography>
Loading your permissions...
</Typography>
}>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations" loading={
<Typography>
Loading your permissions...
</Typography>
}>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
import { ArrayInput, Edit, DateInput, SimpleFormIterator, TextInput } from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
import { Typography } from "@mui/material";
const CustomerEdit = () => (
<Edit>
<LongForm enableAccessControl>
<LongForm.Section id="identity" loading={<Typography>Loading your permissions...</Typography>}>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
</LongForm.Section>
<LongForm.Section id="occupations" loading={<Typography>Loading your permissions...</Typography>}>
<ArrayInput source="occupations" label="">
<SimpleFormIterator>
<TextInput source="name" validate={required()} />
<DateInput source="from" validate={required()} />
<DateInput source="to" />
</SimpleFormIterator>
</ArrayInput>
</LongForm.Section>
</LongForm>
</Edit>
);
cardinality
The cardinality
prop allows to specify a numeral quantity to be displayed next to the section label in the TOC.
import React, { useEffect, useState } from 'react';
import { Edit, TextField } from 'react-admin';
import { LongForm } from '@react-admin/ra-form-layout';
const CustomerEditWithCardinality = () => {
const [publications, setPublications] = useState([]);
useEffect(() => {
setTimeout(() => {
setPublications([
{ id: 1, title: 'Publication 1' },
{ id: 2, title: 'Publication 2' },
{ id: 3, title: 'Publication 3' },
]);
}, 500);
}, []);
return (
<Edit component="div">
<LongForm>
<LongForm.Section label="Identity">...</LongForm.Section>
<LongForm.Section label="Occupations">...</LongForm.Section>
<LongForm.Section label="Preferences">...</LongForm.Section>
<LongForm.Section
label="Publications"
cardinality={publications.length}
>
<ul>
{publications.map(publication => (
<li key={publication.id}>
<TextField
source="title"
record={publication}
/>
</li>
))}
</ul>
</LongForm.Section>
</LongForm>
</Edit>
);
};
import React, { useEffect, useState } from "react";
import { Edit, TextField } from "react-admin";
import { LongForm } from "@react-admin/ra-form-layout";
const CustomerEditWithCardinality = () => {
const [publications, setPublications] = useState([]);
useEffect(() => {
setTimeout(() => {
setPublications([
{ id: 1, title: "Publication 1" },
{ id: 2, title: "Publication 2" },
{ id: 3, title: "Publication 3" },
]);
}, 500);
}, []);
return (
<Edit component="div">
<LongForm>
<LongForm.Section label="Identity">...</LongForm.Section>
<LongForm.Section label="Occupations">...</LongForm.Section>
<LongForm.Section label="Preferences">...</LongForm.Section>
<LongForm.Section label="Publications" cardinality={publications.length}>
<ul>
{publications.map((publication) => (
<li key={publication.id}>
<TextField source="title" record={publication} />
</li>
))}
</ul>
</LongForm.Section>
</LongForm>
</Edit>
);
};
<CreateDialog>
, <EditDialog>
& <ShowDialog>
Sometimes it makes sense to edit or create a resource without leaving the context of the list page.
For those cases, you can use the <CreateDialog>
, <EditDialog>
and <ShowDialog>
components.
They accept a single child which is the form of:
- either a
<SimpleForm>
, a<TabbedForm>
or a custom one (just like the<Create>
and<Edit>
components) for<CreateDialog>
and<EditDialog>
- either a
<SimpleShowLayout>
, a<TabbedShowLayout>
or a custom one (just like the<Show>
component) for<ShowDialog>
Basic Usage, Based On Routing
By default, the Dialog components will use the Router's location to manage their state (open or closed).
This is the easiest way to integrate them in your React-Admin app, because you don't need to manage their state manually. You only need to add them inside your List component.
Here is an example:
import React from 'react';
import {
List,
Datagrid,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
DateInput,
DateField,
required,
ShowButton,
} from 'react-admin';
import {
EditDialog,
CreateDialog,
ShowDialog,
} from '@react-admin/ra-form-layout';
const CustomerList = () => (
<>
<List hasCreate>
<Datagrid rowClick="edit">
...
<ShowButton />
</Datagrid>
</List>
<EditDialog>
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput
source="date_of_birth"
label="born"
validate={required()}
/>
</SimpleForm>
</EditDialog>
<CreateDialog>
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput
source="date_of_birth"
label="born"
validate={required()}
/>
</SimpleForm>
</CreateDialog>
<ShowDialog>
<SimpleShowLayout>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="date_of_birth" label="born" />
</SimpleShowLayout>
</ShowDialog>
</>
);
import React from "react";
import {
List,
Datagrid,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
DateInput,
DateField,
required,
ShowButton,
} from "react-admin";
import { EditDialog, CreateDialog, ShowDialog } from "@react-admin/ra-form-layout";
const CustomerList = () => (
<>
<List hasCreate>
<Datagrid rowClick="edit">
...
<ShowButton />
</Datagrid>
</List>
<EditDialog>
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="date_of_birth" label="born" validate={required()} />
</SimpleForm>
</EditDialog>
<CreateDialog>
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="date_of_birth" label="born" validate={required()} />
</SimpleForm>
</CreateDialog>
<ShowDialog>
<SimpleShowLayout>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="date_of_birth" label="born" />
</SimpleShowLayout>
</ShowDialog>
</>
);
Tip: In the example above, we added the hasCreate
prop to the <List>
component. This is necessary in order to display the "Create" button, because react-admin has no way to know that there exists a creation form for the "customer" resource otherwise.
Note: You can't use the <CreateDialog>
and have a standard <Edit>
specified on your <Resource>
, because the <Routes>
declarations would conflict. If you need this, use the <CreateInDialogButton>
instead.
<CreateInDialogButton>
, <EditInDialogButton>
and <ShowInDialogButton>
In some cases, you might want to use these dialog components outside the List component. For instance, you might want to have an <Edit>
view, including a <Datagrid>
, for which you would like the ability to view, edit or add records using dialog components.
For this purpose, we also provide <CreateInDialogButton>
, <EditInDialogButton>
and <ShowInDialogButton>
.
These components will create a dialog component (<CreateDialog>
, <EditDialog>
or <ShowDialog>
respectively), along with a <Button>
to open them.
These components are also responsible for creating a <FormDialogContext>
, used to manage the dialog's state (open or closed), inside which the dialog component will render.
Here is an implementation example:
import React, { ReactNode } from 'react';
import {
Datagrid,
DateField,
DateInput,
Edit,
ReferenceManyField,
required,
SelectField,
SelectInput,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
useRecordContext,
} from 'react-admin';
import {
CreateInDialogButton,
EditInDialogButton,
ShowInDialogButton,
} from '@react-admin/ra-form-layout';
const sexChoices = [
{ id: 'male', name: 'Male' },
{ id: 'female', name: 'Female' },
];
const CustomerForm = (props: any) => (
<SimpleForm defaultValues={{ firstname: 'John', name: 'Doe' }} {...props}>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</SimpleForm>
);
const CustomerLayout = (props: any) => (
<SimpleShowLayout {...props}>
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="dob" label="born" />
<SelectField source="sex" choices={sexChoices} />
</SimpleShowLayout>
);
// helper component to add actions buttons in a column (children),
// and also in the header (label) of a Datagrid
const DatagridActionsColumn = ({
label,
children,
}: {
label: ReactNode;
children: ReactNode;
}) => <>{children}</>;
const NestedCustomersDatagrid = () => {
const record = useRecordContext();
const createButton = (
<CreateInDialogButton
inline
fullWidth
maxWidth="md"
record={{ employer_id: record?.id }} // pre-populates the employer_id to link the new customer to the current employer
>
<CustomerForm />
</CreateInDialogButton>
);
const editButton = (
<EditInDialogButton fullWidth maxWidth="md">
<CustomerForm />
</EditInDialogButton>
);
const showButton = (
<ShowInDialogButton fullWidth maxWidth="md">
<CustomerLayout />
</ShowInDialogButton>
);
return (
<ReferenceManyField
label="Customers"
reference="customers"
target="employer_id"
>
<Datagrid>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="dob" label="born" />
<SelectField source="sex" choices={sexChoices} />
{/* Using a component as label is a trick to render it in the Datagrid header */}
<DatagridActionsColumn label={createButton}>
{editButton}
{showButton}
</DatagridActionsColumn>
</Datagrid>
</ReferenceManyField>
);
};
const EmployerEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="name" validate={required()} />
<TextInput source="address" validate={required()} />
<TextInput source="city" validate={required()} />
<NestedCustomersDatagrid />
</SimpleForm>
</Edit>
);
import React from "react";
import {
Datagrid,
DateField,
DateInput,
Edit,
ReferenceManyField,
required,
SelectField,
SelectInput,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
useRecordContext,
} from "react-admin";
import { CreateInDialogButton, EditInDialogButton, ShowInDialogButton } from "@react-admin/ra-form-layout";
const sexChoices = [
{ id: "male", name: "Male" },
{ id: "female", name: "Female" },
];
const CustomerForm = (props) => (
<SimpleForm defaultValues={{ firstname: "John", name: "Doe" }} {...props}>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</SimpleForm>
);
const CustomerLayout = (props) => (
<SimpleShowLayout {...props}>
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="dob" label="born" />
<SelectField source="sex" choices={sexChoices} />
</SimpleShowLayout>
);
// helper component to add actions buttons in a column (children),
// and also in the header (label) of a Datagrid
const DatagridActionsColumn = ({ label, children }) => <>{children}</>;
const NestedCustomersDatagrid = () => {
const record = useRecordContext();
const createButton = (
<CreateInDialogButton
inline
fullWidth
maxWidth="md"
record={{ employer_id: record?.id }} // pre-populates the employer_id to link the new customer to the current employer
>
<CustomerForm />
</CreateInDialogButton>
);
const editButton = (
<EditInDialogButton fullWidth maxWidth="md">
<CustomerForm />
</EditInDialogButton>
);
const showButton = (
<ShowInDialogButton fullWidth maxWidth="md">
<CustomerLayout />
</ShowInDialogButton>
);
return (
<ReferenceManyField label="Customers" reference="customers" target="employer_id">
<Datagrid>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="dob" label="born" />
<SelectField source="sex" choices={sexChoices} />
{/* Using a component as label is a trick to render it in the Datagrid header */}
<DatagridActionsColumn label={createButton}>
{editButton}
{showButton}
</DatagridActionsColumn>
</Datagrid>
</ReferenceManyField>
);
};
const EmployerEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="name" validate={required()} />
<TextInput source="address" validate={required()} />
<TextInput source="city" validate={required()} />
<NestedCustomersDatagrid />
</SimpleForm>
</Edit>
);
These components accept the following props:
inline
: set totrue
to display only an MUI<IconButton>
instead of the full<Button>
. The label will still be available as a<Tooltip>
though.icon
: allows to override the default icon.label
: allows to override the default button label. I18N is supported.ButtonProps
: object containing props to pass to MUI's<Button>
.- remaining props will be passed to the corresponding dialog component (
<CreateDialog>
,<EditDialog>
or<ShowDialog>
).
Standalone Usage
<CreateDialog>
, <EditDialog>
and <ShowDialog>
also offer the ability to work standalone, without using the Router's location.
To allow for standalone usage, they require the following props:
isOpen
: a boolean holding the open/close stateopen
: a function that will be called when a component needs to open the dialog (e.g. a button)close
: a function that will be called when a component needs to close the dialog (e.g. the dialog's close button)
Tip: These props are exactly the same as what is stored inside a FormDialogContext
. This means that you can also rather provide your own FormDialogContext
with these values, and render your dialog component inside it, to activate standalone mode.
Below is an example of an <Edit>
page, including a 'create a new customer' button, that opens a fully controlled <CreateDialog>
.
import React, { useCallback, useState } from 'react';
import {
Button,
Datagrid,
DateField,
DateInput,
Edit,
ReferenceManyField,
required,
SelectField,
SelectInput,
SimpleForm,
TextField,
TextInput,
useRecordContext,
} from 'react-admin';
import { CreateDialog } from '@react-admin/ra-form-layout';
const sexChoices = [
{ id: 'male', name: 'Male' },
{ id: 'female', name: 'Female' },
];
const CustomerForm = (props: any) => (
<SimpleForm defaultValues={{ firstname: 'John', name: 'Doe' }} {...props}>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</SimpleForm>
);
const EmployerSimpleFormWithFullyControlledDialogs = () => {
const record = useRecordContext();
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const openCreateDialog = useCallback(() => {
setIsCreateDialogOpen(true);
}, []);
const closeCreateDialog = useCallback(() => {
setIsCreateDialogOpen(false);
}, []);
return (
<SimpleForm>
<TextInput source="name" validate={required()} />
<TextInput source="address" validate={required()} />
<TextInput source="city" validate={required()} />
<Button
label="Create a new customer"
onClick={() => openCreateDialog()}
size="medium"
variant="contained"
sx={{ mb: 4 }}
/>
<CreateDialog
fullWidth
maxWidth="md"
record={{ employer_id: record?.id }} // pre-populates the employer_id to link the new customer to the current employer
isOpen={isCreateDialogOpen}
open={openCreateDialog}
close={closeCreateDialog}
resource="customers"
>
<CustomerForm />
</CreateDialog>
<ReferenceManyField
label="Customers"
reference="customers"
target="employer_id"
>
<Datagrid>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="dob" label="born" />
<SelectField source="sex" choices={sexChoices} />
</Datagrid>
</ReferenceManyField>
</SimpleForm>
);
};
const EmployerEdit = () => (
<Edit>
<EmployerSimpleFormWithFullyControlledDialogs />
</Edit>
);
import React, { useCallback, useState } from "react";
import {
Button,
Datagrid,
DateField,
DateInput,
Edit,
ReferenceManyField,
required,
SelectField,
SelectInput,
SimpleForm,
TextField,
TextInput,
useRecordContext,
} from "react-admin";
import { CreateDialog } from "@react-admin/ra-form-layout";
const sexChoices = [
{ id: "male", name: "Male" },
{ id: "female", name: "Female" },
];
const CustomerForm = (props) => (
<SimpleForm defaultValues={{ firstname: "John", name: "Doe" }} {...props}>
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="dob" label="born" validate={required()} />
<SelectInput source="sex" choices={sexChoices} />
</SimpleForm>
);
const EmployerSimpleFormWithFullyControlledDialogs = () => {
const record = useRecordContext();
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const openCreateDialog = useCallback(() => {
setIsCreateDialogOpen(true);
}, []);
const closeCreateDialog = useCallback(() => {
setIsCreateDialogOpen(false);
}, []);
return (
<SimpleForm>
<TextInput source="name" validate={required()} />
<TextInput source="address" validate={required()} />
<TextInput source="city" validate={required()} />
<Button
label="Create a new customer"
onClick={() => openCreateDialog()}
size="medium"
variant="contained"
sx={{ mb: 4 }}
/>
<CreateDialog
fullWidth
maxWidth="md"
record={{ employer_id: record?.id }} // pre-populates the employer_id to link the new customer to the current employer
isOpen={isCreateDialogOpen}
open={openCreateDialog}
close={closeCreateDialog}
resource="customers"
>
<CustomerForm />
</CreateDialog>
<ReferenceManyField label="Customers" reference="customers" target="employer_id">
<Datagrid>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="dob" label="born" />
<SelectField source="sex" choices={sexChoices} />
</Datagrid>
</ReferenceManyField>
</SimpleForm>
);
};
const EmployerEdit = () => (
<Edit>
<EmployerSimpleFormWithFullyControlledDialogs />
</Edit>
);
title
Unlike the <Create>
, <Edit>
and <Show>
components, with Dialog components the title will be displayed in the <Dialog>
, not in the <AppBar>
.
Still, for <EditDialog>
and <ShowDialog>
, if you pass a custom title component, it will render in the same RecordContext
as the dialog's child component. That means you can display non-editable details of the current record
in the title component.
Here is an example:
import React from 'react';
import {
List,
Datagrid,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
DateInput,
DateField,
required,
ShowButton,
useRecordContext,
} from 'react-admin';
import {
EditDialog,
CreateDialog,
ShowDialog,
} from '@react-admin/ra-form-layout';
const CustomerEditTitle = () => {
const record = useRecordContext();
return record ? (
<span>
Edit {record.last_name} {record.first_name}
</span>
) : null;
};
const CustomerShowTitle = () => {
const record = useRecordContext();
return record ? (
<span>
Show {record.last_name} {record.first_name}
</span>
) : null;
};
const CustomerList = () => (
<>
<List hasCreate>
<Datagrid rowClick="edit">
...
<ShowButton />
</Datagrid>
</List>
<EditDialog title={<CustomerEditTitle />}>
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput
source="date_of_birth"
label="born"
validate={required()}
/>
</SimpleForm>
</EditDialog>
<CreateDialog title="Create a new customer">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput
source="date_of_birth"
label="born"
validate={required()}
/>
</SimpleForm>
</CreateDialog>
<ShowDialog title={<CustomerShowTitle />}>
<SimpleShowLayout>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="date_of_birth" label="born" />
</SimpleShowLayout>
</ShowDialog>
</>
);
import React from "react";
import {
List,
Datagrid,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
DateInput,
DateField,
required,
ShowButton,
useRecordContext,
} from "react-admin";
import { EditDialog, CreateDialog, ShowDialog } from "@react-admin/ra-form-layout";
const CustomerEditTitle = () => {
const record = useRecordContext();
return record ? (
<span>
Edit {record.last_name} {record.first_name}
</span>
) : null;
};
const CustomerShowTitle = () => {
const record = useRecordContext();
return record ? (
<span>
Show {record.last_name} {record.first_name}
</span>
) : null;
};
const CustomerList = () => (
<>
<List hasCreate>
<Datagrid rowClick="edit">
...
<ShowButton />
</Datagrid>
</List>
<EditDialog title={<CustomerEditTitle />}>
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="date_of_birth" label="born" validate={required()} />
</SimpleForm>
</EditDialog>
<CreateDialog title="Create a new customer">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="date_of_birth" label="born" validate={required()} />
</SimpleForm>
</CreateDialog>
<ShowDialog title={<CustomerShowTitle />}>
<SimpleShowLayout>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="date_of_birth" label="born" />
</SimpleShowLayout>
</ShowDialog>
</>
);
Customizing The Dialog
You can also pass the props accepted by the MUI <Dialog>
component, like fullWidth
or maxWidth
, directly to <CreateDialog>
, <EditDialog>
or <ShowDialog>
.
import React from 'react';
import {
List,
Datagrid,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
DateInput,
DateField,
required,
ShowButton,
} from 'react-admin';
import {
EditDialog,
CreateDialog,
ShowDialog,
} from '@react-admin/ra-form-layout';
const CustomerList = () => (
<>
<List hasCreate>
<Datagrid rowClick="edit">
...
<ShowButton />
</Datagrid>
</List>
<EditDialog fullWidth maxWidth="md">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput
source="date_of_birth"
label="born"
validate={required()}
/>
</SimpleForm>
</EditDialog>
<CreateDialog fullWidth maxWidth="md">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput
source="date_of_birth"
label="born"
validate={required()}
/>
</SimpleForm>
</CreateDialog>
<ShowDialog fullWidth maxWidth="md">
<SimpleShowLayout>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="date_of_birth" label="born" />
</SimpleShowLayout>
</ShowDialog>
</>
);
import React from "react";
import {
List,
Datagrid,
SimpleForm,
SimpleShowLayout,
TextField,
TextInput,
DateInput,
DateField,
required,
ShowButton,
} from "react-admin";
import { EditDialog, CreateDialog, ShowDialog } from "@react-admin/ra-form-layout";
const CustomerList = () => (
<>
<List hasCreate>
<Datagrid rowClick="edit">
...
<ShowButton />
</Datagrid>
</List>
<EditDialog fullWidth maxWidth="md">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="date_of_birth" label="born" validate={required()} />
</SimpleForm>
</EditDialog>
<CreateDialog fullWidth maxWidth="md">
<SimpleForm>
<TextField source="id" />
<TextInput source="first_name" validate={required()} />
<TextInput source="last_name" validate={required()} />
<DateInput source="date_of_birth" label="born" validate={required()} />
</SimpleForm>
</CreateDialog>
<ShowDialog fullWidth maxWidth="md">
<SimpleShowLayout>
<TextField source="id" />
<TextField source="first_name" />
<TextField source="last_name" />
<DateField source="date_of_birth" label="born" />
</SimpleShowLayout>
</ShowDialog>
</>
);
Redirection After Deletion
If you use <SimpleForm>
as child of <EditDialog>
or <EditInDialogButton>
, the default form toolbar includes a <DeleteButton>
. And upon deletion, this button redirects to the current resource list. This is probably not what you want, so it's common to customize the form toolbar to disable the redirection after deletion:
// src/CustomToolbar.tsx
import { Toolbar, SaveButton, DeleteButton } from 'react-admin';
export const CustomToolbar = () => (
<Toolbar sx={{ justifyContent: 'space-between' }}>
<SaveButton />
<DeleteButton redirect={false} />
</Toolbar>
);
// src/EmployerEdit.tsx
import { Edit, SimpleForm, TextInput, ReferenceManyField } from 'react-admin';
import {