API

The API consists of a few items:

Pages

A page represents a single page of a form.

Type

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.
};

Example

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>;
	}
}

Sequence

A sequence contains pages or more sequences that represent a single workflow. They can be nested infinitely.

Type

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.
};

Decision Nodes

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
}

Example

const mySequence: FormSequence<MyData, MyComponentProps, FieldError> = {
	id: 'my-sequence',
	isRequired: (data) => !!data.fieldToCheck,
	pages: [myPage, myPage2, myOtherSequence],
}

useMultiPageForm and useMultiPageHookForm React hooks

Both 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.

Type

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.
};

Example

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>
        )}
    </>);
}

© 2024 Stu Kabakoff