Skip to content

Stepper Context and Usage

This documentation provides a complete overview of the MultiStepForm component, the GenericStepper context, and a step-by-step guide to creating your own custom multi-step forms. This system is designed to be highly flexible and type-safe, allowing for the creation of complex, multi-step user input flows with minimal boilerplate.


The MultiStepForm is a generic and reusable React component that orchestrates a multi-step form experience. It manages the display of steps, navigation between them, data accumulation, and final submission. It’s designed to be decoupled from the state management logic, which is provided via a context system.

These are the props accepted by the MultiStepForm component:

PropTypeDescription
stepsStepConfig<T>[]An ordered array of step configuration objects. Each object defines a label for the indicator and the component to render for that step.
onFinish(data: T) => voidA callback function that is invoked with the accumulated form data when the user clicks the “Submit” button on the final step.
ContextProviderReact.FC<{ initialData?: T; children: React.ReactNode }>The Provider component from your created stepper context. This will wrap the form and provide the state.
useStepperHook() => StepperContextValue<T>The hook from your created stepper context. The form uses this to access state and control navigation.
initialDataT (optional)An optional object containing the initial data for the form.

The MultiStepForm component relies on several TypeScript types to ensure type safety and a clear development experience.

This interface defines the props that are automatically passed to every step component you create.

  • data: T: The current, accumulated data object for the entire form. T is a generic type representing your form’s data structure.
  • update: (u: Partial<T>) => void: A function to update the form’s data. You pass it a partial object of your data structure, and it will be merged into the current state.

This is a type alias for a React component that conforms to the required props structure for a step.

TypeScript

export type FormStep<T> = React.ComponentType<CommonStepProps<T>>;

This interface defines the shape of the objects you’ll use to configure each step in the steps array.

  • label: string: The text label for the step, which is displayed in the StepIndicator.
  • component: FormStep<T>: The React component that will be rendered for this step.

The MultiStepForm contains two key internal components:

A visual component that displays the user’s progress through the form steps. It renders a series of numbered circles, highlighting the current, completed, and upcoming steps.

This is the core functional component that:

  • Renders the current active step component.
  • Provides “Back” and “Next”/“Submit” buttons for navigation.
  • Handles form validation using the browser’s built-in HTML5 validation API before proceeding to the next step.
  • Calls the onFinish function on the final step.

The GenericStepper provides the state management for the MultiStepForm through a factory function that creates a typed React Context, Provider, and hook.

This factory function is the heart of the state management system. You call it once per form type to generate a dedicated set of context tools.

  • Generic T: You must provide a type argument that represents the data structure of your form (e.g., createStepperContext<BusinessDetail>()).

Returns: An object containing:

  • Provider: A React component that you’ll use to wrap your MultiStepForm. It initializes and manages the form’s state (activeStep, data).
  • useStepper: A custom hook that allows your step components to access and manipulate the form’s state.

This interface defines the shape of the value provided by the stepper context.

  • activeStep: number: The index of the currently active step.
  • setActiveStep: (step: number) => void: A function to programmatically change the active step.
  • data: T: The accumulated data object for the form.
  • update: (partial: Partial<T>) => void: A function to merge partial updates into the form data.

Complete Usage Guide: Creating a Custom Multi-Step Form

Section titled “Complete Usage Guide: Creating a Custom Multi-Step Form”

Here’s how to use these components together to create a fully functional, custom multi-step form.

First, create an interface or type that defines the shape of the data you want to collect.

Example: BusinessDetail.ts

TypeScript

export interface BusinessDetail {
name: string;
clientNumber: string;
logoUrl: string;
summary: string;
industryType: string;
website: string;
employeeCount: number;
// ... add other fields as needed
}

Next, use the createStepperContext factory to generate a specific Provider and hook for your form data type.

Example: businessStepper.ts

TypeScript

import { createStepperContext } from "@/components/MultiStepForm/GenericStepper";
import { BusinessDetail } from "../data/businessDetail";
// Create and export a dedicated Provider and hook
export const {
Provider: BusinessStepperProvider,
useStepper: useBusinessStepper,
} = createStepperContext<BusinessDetail>();

Now, create the individual React components for each step of your form. Each component must be of type FormStep<T> and will receive data and update as props.

Example: Step1_Basic.tsx

TypeScript

import React from "react";
import { FormStep } from "@/components/MultiStepForm";
import { BusinessDetail } from "@/modules/BusinessManager/data/businessDetail";
const Step1_Basic: FormStep<BusinessDetail> = ({ data, update }) => {
return (
<>
<legend>Business Info</legend>
<label>
Name
<input
value={data.name ?? ""}
onChange={(e) => update({ name: e.target.value })}
type="text"
required
/>
</label>
<label>
Client Number
<input
value={data.clientNumber ?? ""}
onChange={(e) => update({ clientNumber: e.target.value })}
type="text"
required
/>
</label>
{/* ... other input fields ... */}
</>
);
};
export default Step1_Basic;

Create an array of StepConfig<T> objects. This array defines the order of your steps, their labels, and the component to render for each one.

Example: In your form implementation file

TypeScript

import { StepConfig } from "@/components/MultiStepForm";
import { BusinessDetail } from "@/modules/BusinessManager/data/businessDetail";
import Step1_Basic from "./Step1_Basic";
import Step2_Contacts from "./Step2_Contacts";
// ... import other steps
const STEPS: Array<StepConfig<BusinessDetail>> = [
{ label: "Basic", component: Step1_Basic },
{ label: "Contacts", component: Step2_Contacts },
// ... other step configurations
] as const;

Finally, put everything together by rendering the MultiStepForm component. Pass it the steps array, your onFinish handler, and the custom ContextProvider and useStepperHook you created.

Example: NewBusinessForm.tsx

TypeScript

import { MultiStepForm, StepConfig } from "@/components/MultiStepForm";
import { BusinessDetail } from "@/modules/BusinessManager/data/businessDetail";
import {
BusinessStepperProvider,
useBusinessStepper,
} from "@/modules/BusinessManager/context/businessStepper";
import Step1_Basic from "./Step1_Basic";
// ... other imports
// Define the steps array
const STEPS: Array<StepConfig<BusinessDetail>> = [
{ label: "Basic", component: Step1_Basic },
// ... other steps
];
interface NewBusinessFormProps {
onClose?: () => void;
onSubmit: (data: BusinessDetail) => void;
}
export default function NewBusinessForm({
onClose,
onSubmit,
}: NewBusinessFormProps) {
return (
// You can wrap the form in other components like a modal
<FormModal onClose={onClose} title="Add a new business">
<MultiStepForm<BusinessDetail>
steps={STEPS}
onFinish={onSubmit}
ContextProvider={BusinessStepperProvider}
useStepperHook={useBusinessStepper}
/>
</FormModal>
);
}

By following these steps, you can easily create robust and maintainable multi-step forms tailored to any data structure you need.