The API consists of a few items:
useMultiPageForm
and useMultiPageHookForm
React hooksA page represents a single page of a form.
type HookFormPage<DataT, ComponentProps = { hookForm: UseFormReturn }> = {
// these three are required
id: string; // Unique identifier for the form page.
Component: (props: ComponentProps) => JSX.Element; // React component to render the form page.
isComplete: (data: DeepPartial<DataT>) => boolean; // Checks if the user has already filled out this page.
// these manage the sequence
isFinal?: (data: DeepPartial<DataT>) => boolean; // return true if this should be the final page of the form.
isRequired?: (data: DeepPartial<DataT>) => boolean | undefined; // Determines if this page is needed based on form data. Default: () => true
validate?: (data: DeepPartial<DataT>) => Promise<FieldErrors | undefined>; // Determines whether or not to continue.
selectNextPage?: (data: DeepPartial<DataT>) => pageId; // Useful if you need to override the default order of pages
// event handlers
onArrive?: (data: DeepPartial<DataT>) => void; // Function to execute upon arriving at this page.
onExit?: (data: DeepPartial<DataT>) => Promise<void> | void; // Function to execute when exiting the page.
};
type MyComponentProps = {
register: UseFormRegister<MyData>;
}
const myPage: HookFormPage<MyData> = {
id: 'my-page',
isComplete: (data) => !!data.myField,
Component: ({register}) => {
return <div>
Name:
<input type="text" {...register('name')} />
</div>;
}
}
A sequence contains pages or more sequences that represent a single workflow. They can be nested infinitely.
export type FormSequence<DataT, ComponentProps, ErrorList> = {
id: string; // Unique identifier for the form sequence.
pages: HookFormSequenceChild[]; // A SequenceChild is either a FormPage or a FormSequence.
isRequired?: (data: DeepPartial<DataT>) => boolean | undefined; // Determines if the sequence is needed based on form data.
};
If you need to make a decision on which page comes next isolate from a page the user will see, decision nodes will help.
export type DecisionNode<DataT> = {
id: string,
// determines whether or not this decision is needed
isRequired?: (data: DeepPartial<DataT>) => boolean | undefined
// where this node should redirect to. Undefined will go to the next page in the sequence
selectNextPage?: (data: DeepPartial<DataT>) => Boolean
}
const mySequence: FormSequence<MyData, MyComponentProps, FieldError> = {
id: 'my-sequence',
isRequired: (data) => !!data.fieldToCheck,
pages: [myPage, myPage2, myOtherSequence],
}
useMultiPageForm
and useMultiPageHookForm
React hooksBoth hooks return the same controller object, and take almost the exact same parameters, but useMultiPageHookForm
is preconfigured to work well with React Hook Form. If you need to integrate with a different library, useMultiPageForm
will work well.
export type MultiPageHookFormParams<DataT> = {
pages: HookFormSequenceChild[]; // Array of form pages or nested sequences.
startingPage?: string | StartingPage; // Specifies the starting page, default is StartingPage.FirstIncomplete
onBeforePageChange?: (
data: DataT,
page: HookFormPage<DataT>,
) => Promise<FieldErrors | boolean>; // Callback before changing pages, returns error list or boolean to proceed.
onPageChange?: (
data: DataT,
newPage: HookFormPage<DataT>,
) => void; // Callback when navigating to a new page.
onComplete?: (data: DataT) => void; // Callback when the form is completed.
onValidationError?: (errorList: FieldErrors) => void; // Callback when validation errors occur.
};
import { useForm, UseFormRegister } from 'react-hook-form';
import { useMultiPageHookForm, StartingPage } from 'react-multi-page-form/hookForm';
// this is the data model for the flow
type MyDataType = {
name: string,
location: string,
}
// these are the props for each component in the flow.
// The default is { hookForm: UseFormReturn }
type MyComponentProps = {
theme: string,
hookForm: UseFormReturn
}
export function MyMultiPageForm() {
// use react-hook-form's useForm
const hookForm = useForm<MyDataType>();
// create multi-page controls
const {
currentPage, // the current page object
advance, // goes to the next page
advanceToNextIncomplete, // advances, skipping pages that are complete
goBack, // goes back one page
goTo, // goes to a page by id
isFinal, // if this is the last page
isFirst, // if this is the first page in the sequence
} = useMultiPageHookForm<MyDataType, MyComponentProps>({
hookForm,
pages: [myPage, mySequence],
});
return (<>
<currentPage.Component
theme="dark"
hookForm={hookForm}
/>
{!isFirst && <button onClick={goBack}>Prev</button>}
{!isFinal ? (
<button onClick={advance}>Next</button>
) : (
<button type="submit">Submit</button>
)}
</>);
}