This guide will help you get started using React Multi Page Form in combination with React Hook Form and TypeScript. For other form libraries, such as Formik or React Final Form, you will need to implement page level validation in an onBeforePageChange
listener and a getCurrentData
method. If you're not using typescript, you can skip the schema and component prop types.
This guide assumes that you already have a flow to implement, a form library, types for your data, and a method of saving data.
The basic steps are:
npm install react-multi-page-form react-hook-form
yarn add react-multi-page-form react-hook-form
To allow consistent typing throughout the library, you need a single type for your data and a single type for your component props.
This is the shape the data takes for the entirety of the form. Not all of it will be on the screen at the same time, and you'll have a chance to save data when changing pages. This data will be DeepPartial'd, since it won't all be available until the user is done providing it.
type MySchema = {
name: string;
location: string;
...
}
If you need to provide configuration that is not part of the form data, such as A/B testing or themeing, you can provide this as props to each element. By default, each component will receive a single prop: hookForm
, which is the return value of React Hook Form's useForm
, the type is UseFormReturn<MySchema>
.
type MyComponentProps = {
hookForm: UseFormReturn<MyDataType>;
myOtherProp: string;
}
// an example component for the page.
const MyComponent = (props: MyComponentProps) => {
const {
register,
formState
} = props.hookForm
return <div>
<input type="text" {...register('name', {required: true})}>
{formState.errors.name && <span className="error">{formState.errors.name.message}</span>}
</div>
}
Note that you do not need to, and should not, include the form data in the props, as it will cause the page to re-render frequently.
When designing complex workflows, there may be paths where certain pages or sequences are skipped. Often this is represented in a flow chart. It is recommended that each branch of a flow chart that has more than one page gets represented as a sequence. See the docs for complex flows for help transforming your flow into sequences.
import { HookFormPage } from 'react-multi-page-forms';
import { LocationComponent } from './components/locationComponent';
// the page object
const locationPage: HookFormPage<MyDataType, MyComponentProps> = {
id: 'location-page',
// this is used to determine if we need to show this page to the user at all.
// if it's not provided, it's assumend that the page is required.
isRequired: (data) => !!data.isEarthResident,
// this method is used to determine if we need to show the user this page again after resuming.
isComplete: (data) => !!data.location,
Component: LocationPageComponent
}
Sequences allow you to bundle multiple pages together. For example, if a user lives on Earth, you might want to ask them about their location, favorite food, and pets.
Here's how you create a sequence for those pages:
import { HookFormSequence } from 'react-multi-page-forms';
import { locationPage, foodPage, petPage } from './pages/earth';
const earthSequence: HookFormSequence<MyDataType, MyComponentProps> = {
id: 'earth-sequece',
isRequired: (data) => !!data.personLivesOnEarth,
pages: [
locationPage,
foodPage,
petPage
]
}
import { useForm } from 'react-hook-form';
import { useMultiPageHookForm } from 'react-multi-page-forms';
import type { FormSchema, ComponentProps } from './types';
import { earthSequence } from './earthSequence';
const MyMultiPageForm = () => {
// use react-hook-form's useForm
const hookForm = useForm<FormSchema>();
// create multi-page controls
const {
currentPage, // the page object
advance, // goes to the next page
goBack, // goes back one page
isFinal, // if this is the last page
isFirst, // if this is the first page
} = useMultiPageHookForm<FormSchema>({
hookForm,
pages: [mySequence, otherPage],
});
// render the component and controls
return (<>
<currentPage.Component
hookForm={hookForm}
/>
{!isFirst && <button onClick={goBack}>Prev</button>}
{!isFinal ? (
<button onClick={advance}>Next</button>
) : (
<button type="submit">Submit</button>
)}
</>);
};