<WizardForm>
ThisEnterprise Edition component offers an alternative layout for large Create forms, allowing users to enter data step-by-step.
<WizardForm>
renders one step at a time. The form is submitted when the user clicks on theSave
button of the last step.
Usage
Use<WizardForm>
as the child of<Create>
. It expects<WizardForm.Step>
elements as children.
import{Create,TextInput,required}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';constPostCreate=()=>(<Create><WizardForm><WizardForm.Steplabel="First step"><TextInputsource="title"validate={required()}/></WizardForm.Step><WizardForm.Steplabel="Second step"><TextInputsource="description"/></WizardForm.Step><WizardForm.Steplabel="Third step"><TextInputsource="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: You can use the<AutoSave>
component to automatically save the form after a few seconds of inactivity. Seethe AutoSave documentation for details.
Props
The<WizardForm>
component accepts the following props:
Prop | Required | Type | Default | Description |
---|---|---|---|---|
authorizationError | Optional | ReactNode | null | The content to display when authorization checks fail |
children | Required | ReactNode | - | A list of<WizardForm.Step> elements. |
defaultValues | Optional | object|function | - | The default values of the record. |
enableAccessControl | Optional | boolean | false | Enable checking authorization rights for each panel and input |
id | Optional | string | - | The id of the underlying<form> tag. |
loading | Optional | ReactNode | The content to display when checking authorizations | |
noValidate | Optional | boolean | - | Set totrue to disable the browser’s default validation. |
onSubmit | Optional | function | save | A callback to call when the form is submitted. |
progress | Optional | ReactElement | - | A custom progress stepper element. |
sanitize EmptyValues | Optional | boolean | - | Set totrue to remove empty values from the form state. |
toolbar | Optional | ReactElement | - | A custom toolbar element. |
validate | Optional | function | - | A function to validate the form values. |
warnWhen UnsavedChanges | Optional | boolean | - | Set totrue to warn the user when leaving the form with unsaved changes. |
Additional props are passed toreact-hook-form
’suseForm
hook.
authorizationError
Used whenenableAccessControl
is set totrue
and an error occurs while checking for users permissions. Defaults tonull
:
import{ArrayInput,Edit,DateInput,SimpleFormIterator,TextInput}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';import{Alert}from'@mui/material';constCustomerEdit=()=>(<Edit><WizardFormenableAccessControlauthorizationError={<Alertseverity="error"sx={{px:2.5,py:1,mt:1,width:'100%'}}> An error occurred while loading your permissions</Alert>}><WizardForm.Stepid="identity"><TextInputsource="first_name"validate={required()}/><TextInputsource="last_name"validate={required()}/></WizardForm.Step><WizardForm.Stepid="occupations"><ArrayInputsource="occupations"label=""><SimpleFormIterator><TextInputsource="name"validate={required()}/><DateInputsource="from"validate={required()}/><DateInputsource="to"/></SimpleFormIterator></ArrayInput></WizardForm.Step></WizardForm></Edit>);
children
The children of<WizardForm>
must be<WizardForm.Step>
elements.
constPostCreate=()=>(<Create><WizardForm><WizardForm.Steplabel="First step"> ...</WizardForm.Step><WizardForm.Steplabel="Second step"> ...</WizardForm.Step><WizardForm.Steplabel="Third step"> ...</WizardForm.Step></WizardForm></Create>);
defaultValues
The value of the formdefaultValues
prop is an object, or a function returning an object, specifying default values for the created record. For instance:
constpostDefaultValue=()=>({id:uuid(),created_at:newDate(),nb_views:0});exportconstPostCreate=()=>(<Create><WizardFormdefaultValues={postDefaultValue}><WizardForm.Step><TextInputsource="title"/><RichTextInputsource="body"/><NumberInputsource="nb_views"/><SaveButton/></WizardForm.Step></WizardForm></Create>);
Tip: You can include properties in the formdefaultValues
that are not listed as input components, like thecreated_at
property in the previous example.
Tip: React-admin also allows to define default values at the input level. See theSetting default Values section.
enableAccessControl
When set totrue
, React-admin will call theauthProvider.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 theauthProvider.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 asource
will always be displayed.
import{ArrayInput,Edit,DateInput,SimpleFormIterator,TextInput}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';constCustomerEdit=()=>(<Edit><WizardFormenableAccessControl><WizardForm.Stepid="identity"><TextInputsource="first_name"validate={required()}/><TextInputsource="last_name"validate={required()}/></WizardForm.Step><WizardForm.Stepid="occupations"><ArrayInputsource="occupations"label=""><SimpleFormIterator><TextInputsource="name"validate={required()}/><DateInputsource="from"validate={required()}/><DateInputsource="to"/></SimpleFormIterator></ArrayInput></WizardForm.Step></WizardForm></Edit>);
id
Normally, a submit button only works when placed inside a<form>
tag. However, you can place a submit button outside the form if the submit buttonform
matches the formid
.
Set this formid
via theid
prop.
exportconstPostCreate=()=>(<Create><WizardFormdefaultValues={postDefaultValue}id="post_create_form"><WizardForm.Step><TextInputsource="title"/><RichTextInputsource="body"/><NumberInputsource="nb_views"/></WizardForm.Step></WizardForm><SaveButtonform="post_create_form"/></Create>);
loading
Used whenenableAccessControl
is set totrue
while checking for users permissions. Defaults toLoading
fromreact-admin
:
import{ArrayInput,Edit,DateInput,SimpleFormIterator,TextInput}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';import{Typography}from'@mui/material';constCustomerEdit=()=>(<Edit><WizardFormenableAccessControlloading={<Typography> Loading your permissions...</Typography>}><WizardForm.Stepid="identity"><TextInputsource="first_name"validate={required()}/><TextInputsource="last_name"validate={required()}/></WizardForm.Step><WizardForm.Stepid="occupations"><ArrayInputsource="occupations"label=""><SimpleFormIterator><TextInputsource="name"validate={required()}/><DateInputsource="from"validate={required()}/><DateInputsource="to"/></SimpleFormIterator></ArrayInput></WizardForm.Step></WizardForm></Edit>);
noValidate
The<form novalidate>
attribute prevents the browser from validating the form. This is useful if you don’t want to use the browser’s default validation, or if you want to customize the error messages. To set this attribute on the underlying<form>
tag, set thenoValidate
prop totrue
.
constPostCreate=()=>(<Create><WizardFormnoValidate> ...</WizardForm></Create>);
onSubmit
By default, the<Form>
calls thesave
callback passed to it by the edit or create controller, via theSaveContext
. You can override this behavior by setting a callback as theonSubmit
prop manually.
exportconstPostCreate=()=>{const[create]=useCreate();constpostSave=(data)=>{create('posts',{data});};return(<Create><WizardFormonSubmit={postSave}> ...</WizardForm></Create>);};
progress
You can also customize the progress stepper by passing a custom component in theprogress
prop.
importReactfrom'react';import{Create,TextInput,required}from'react-admin';import{WizardForm,WizardFormProgressProps,useWizardFormContext}from'@react-admin/ra-form-layout';constMyProgress=(props:WizardFormProgressProps)=>{const{currentStep,steps}=useWizardFormContext(props);return(<ul>{steps.map((step,index)=>{constlabel=React.cloneElement(step,{intent:'label'});return(<likey={`step_${index}`}><spanstyle={{textDecoration:currentStep===index?'underline':undefined,}}>{label}</span></li>);})}</ul>);};constPostCreate=()=>(<Create><WizardFormprogress={<MyProgress/>}><WizardForm.Steplabel="First step"><TextInputsource="title"validate={required()}/></WizardForm.Step><WizardForm.Steplabel="Second step"><TextInputsource="description"/></WizardForm.Step><WizardForm.Steplabel="Third step"><TextInputsource="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 settingprogress
tofalse
.
importReactfrom'react';import{Create,TextInput,required}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';constPostCreate=()=>(<Create><WizardFormprogress={false}><WizardForm.Steplabel="First step"><TextInputsource="title"validate={required()}/></WizardForm.Step><WizardForm.Steplabel="Second step"><TextInputsource="description"/></WizardForm.Step><WizardForm.Steplabel="Third step"><TextInputsource="fullDescription"validate={required()}/></WizardForm.Step></WizardForm></Create>);
sanitizeEmptyValues
In HTML, the value of empty form inputs is the empty string (''
). React-admin inputs (like<TextInput>
,<NumberInput>
, etc.) automatically transform these empty values intonull
.
But for your own input components based on react-hook-form, this is not the default. React-hook-form doesn’t transform empty values by default. This leads to unexpectedcreate
andupdate
payloads like:
{id:1234,title:'Lorem Ipsum',is_published:'',body:'',// etc.}
If you prefer to omit the keys for empty values, set thesanitizeEmptyValues
prop totrue
. This will sanitize the form data before passing it to thedataProvider
, i.e. remove empty strings from the form state, unless the record actually had a value for that field before edition.
constPostCreate=()=>(<Create><WizardFormsanitizeEmptyValues> ...</WizardForm></Create>);
For the previous example, the data sent to thedataProvider
will be:
{id:1234,title:'Lorem Ipsum',}
Note: Setting thesanitizeEmptyValues
prop totrue
will also have a (minor) impact on react-admin inputs (like<TextInput>
,<NumberInput>
, etc.): empty values (i.e. values equal tonull
) will be removed from the form state on submit, unless the record actually had a value for that field.
Note Even withsanitizeEmptyValues
set totrue
, deeply nested fields won’t be set tonull
nor removed. If you need to sanitize those fields, usethetransform
prop of<Edit>
or<Create>
components.
If you need a more fine-grained control over the sanitization, you can usethetransform
prop of<Edit>
or<Create>
components, ortheparse
prop of individual inputs.
toolbar
You can customize the form toolbar by passing a custom component in thetoolbar
prop.
import{Button}from'@mui/material';importReactfrom'react';import{Create,required,TextInput,useSaveContext}from'react-admin';import{useFormState}from'react-hook-form';import{useWizardFormContext,WizardForm}from'@react-admin/ra-form-layout';constMyToolbar=()=>{const{hasNextStep,hasPreviousStep,goToNextStep,goToPreviousStep}=useWizardFormContext();const{save}=useSaveContext();const{isValidating}=useFormState();return(<ul>{hasPreviousStep?(<li><ButtononClick={()=>goToPreviousStep()}>PREVIOUS</Button></li>):null}{hasNextStep?(<li><Buttondisabled={isValidating}onClick={()=>goToNextStep()}> NEXT</Button></li>):(<li><Buttondisabled={isValidating}onClick={save}> SAVE</Button></li>)}</ul>);};constPostCreate=()=>(<Create><WizardFormtoolbar={<MyToolbar/>}><WizardForm.Steplabel="First step"><TextInputsource="title"validate={required()}/></WizardForm.Step><WizardForm.Steplabel="Second step"><TextInputsource="description"/></WizardForm.Step><WizardForm.Steplabel="Third step"><TextInputsource="fullDescription"validate={required()}/></WizardForm.Step></WizardForm></Create>);
validate
The value of the formvalidate
prop must be a function taking the record as input, and returning an object with error messages indexed by field. For instance:
constvalidateUserCreation=(values)=>{consterrors={};if(!values.firstName){errors.firstName='The firstName is required';}if(!values.age){// You can return translation keyserrors.age='ra.validation.required';}elseif(values.age<18){// Or an object if the translation messages need parameterserrors.age={message:'ra.validation.minValue',args:{min:18}};}returnerrors};exportconstUserCreate=()=>(<Create><WizardFormvalidate={validateUserCreation}><WizardForm.Step><TextInputlabel="First Name"source="firstName"/><TextInputlabel="Age"source="age"/></WizardForm.Step></WizardForm></Create>);
Tip: Thevalidate
function can return a promise for asynchronous validation. Seethe Server-Side Validation section in the Validation documentation.
Tip: React-admin also allows to define validation rules at the input level. Seethe Validation chapter for details.
warnWhenUnsavedChanges
React-admin keeps track of the form state, so it can detect when the user leaves anEdit
orCreate
page with unsaved changes. To avoid data loss, you can use this ability to ask the user to confirm before leaving a page with unsaved changes.
Warning about unsaved changes is an opt-in feature: you must set thewarnWhenUnsavedChanges
prop in the form component to enable it:
exportconstTagEdit=()=>(<Edit><WizardFormwarnWhenUnsavedChanges> ...</WizardForm></Edit>);
Note: Due to limitations in react-router, this feature only works if you use the default router provided by react-admin, or if you use aData Router.
<WizardForm.Step>
Thelabel
prop of the<WizardForm.Step>
component accepts a translation key:
importReactfrom'react';import{Create,TextInput,required}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';constPostCreate=()=>(<Create><WizardForm><WizardForm.Steplabel="myapp.posts.steps.general"><TextInputsource="title"validate={required()}/></WizardForm.Step><WizardForm.Steplabel="myapp.posts.steps.description"><TextInputsource="description"/></WizardForm.Step><WizardForm.Steplabel="myapp.posts.steps.misc"><TextInputsource="fullDescription"validate={required()}/></WizardForm.Step></WizardForm></Create>);
The children of<WizardForm>
must be<WizardForm.Step>
elements.
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 whenenableAccessControl
is set totrue
and an error occurs while checking for users permissions. Defaults tonull
:
import{ArrayInput,Edit,DateInput,SimpleFormIterator,TextInput}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';import{Alert}from'@mui/material';constCustomerEdit=()=>(<Edit><WizardFormenableAccessControl><WizardForm.Stepid="identity"><TextInputsource="first_name"validate={required()}/><TextInputsource="last_name"validate={required()}/></WizardForm.Step><WizardForm.Stepid="occupations"authorizationError={<Alertseverity="error"sx={{px:2.5,py:1,mt:1,width:'100%'}}> An error occurred while loading your permissions</Alert>}><ArrayInputsource="occupations"label=""><SimpleFormIterator><TextInputsource="name"validate={required()}/><DateInputsource="from"validate={required()}/><DateInputsource="to"/></SimpleFormIterator></ArrayInput></WizardForm.Step></WizardForm></Edit>);
enableAccessControl
When set totrue
, react-admin will also call theauthProvider.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 asource
will always be displayed.
import{ArrayInput,Edit,DateInput,SimpleFormIterator,TextInput}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';constCustomerEdit=()=>(<Edit><WizardForm><WizardForm.Stepid="identity"><TextInputsource="first_name"validate={required()}/><TextInputsource="last_name"validate={required()}/></WizardForm.Step><WizardForm.Stepid="occupations"enableAccessControl><ArrayInputsource="occupations"label=""><SimpleFormIterator><TextInputsource="name"validate={required()}/><DateInputsource="from"validate={required()}/><DateInputsource="to"/></SimpleFormIterator></ArrayInput></WizardForm.Step></WizardForm></Edit>);
loading
Used whenenableAccessControl
is set totrue
while checking for users permissions. Defaults toLoading
fromreact-admin
:
import{ArrayInput,Edit,DateInput,SimpleFormIterator,TextInput}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';import{Typography}from'@mui/material';constCustomerEdit=()=>(<Edit><WizardFormenableAccessControl><WizardForm.Stepid="identity"loading={<Typography> Loading your permissions...</Typography>}><TextInputsource="first_name"validate={required()}/><TextInputsource="last_name"validate={required()}/></WizardForm.Step><WizardForm.Stepid="occupations"loading={<Typography> Loading your permissions...</Typography>}><ArrayInputsource="occupations"label=""><SimpleFormIterator><TextInputsource="name"validate={required()}/><DateInputsource="from"validate={required()}/><DateInputsource="to"/></SimpleFormIterator></ArrayInput></WizardForm.Step></WizardForm></Edit>);
Adding a Summary Final Step
In order to add a final step with a summary of the form values before submit, you can leveragereact-hook-form
useWatch
hook:
constFinalStepContent=()=>{constvalues=useWatch({name:['title','description','fullDescription'],});returnvalues?.length>0?(<><Typography>title:{values[0]}</Typography><Typography>description:{values[1]}</Typography><Typography>fullDescription:{values[2]}</Typography></>):null;};constPostCreate=()=>(<Create><WizardForm><WizardForm.Steplabel="First step"><TextInputsource="title"validate={required()}/></WizardForm.Step><WizardForm.Steplabel="Second step"><TextInputsource="description"/></WizardForm.Step><WizardForm.Steplabel="Third step"><TextInputsource="fullDescription"validate={required()}/></WizardForm.Step><WizardForm.Steplabel=""><FinalStepContent/></WizardForm.Step></WizardForm></Create>);
Access Control
<WizardForm>
can useAccess Control to check permissions for each section and input. To enable this feature, set theenableAccessControl
prop totrue
.
Check theenableAccessControl
prop section for more details.
import{ArrayInput,Edit,DateInput,SimpleFormIterator,TextInput}from'react-admin';import{WizardForm}from'@react-admin/ra-form-layout';constCustomerEdit=()=>(<Edit><WizardFormenableAccessControl><WizardForm.Stepid="identity"><TextInputsource="first_name"validate={required()}/><TextInputsource="last_name"validate={required()}/></WizardForm.Step><WizardForm.Stepid="occupations"><ArrayInputsource="occupations"label=""><SimpleFormIterator><TextInputsource="name"validate={required()}/><DateInputsource="from"validate={required()}/><DateInputsource="to"/></SimpleFormIterator></ArrayInput></WizardForm.Step></WizardForm></Edit>);