npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rhf-wizard

v1.0.1

Published

Multistep wizard for React Hook Form

Downloads

422

Readme

React Hook Form Wizard

React multistep wizard library with React Hook Form integration.

This library let's you build multistep form wizards using React Hook Form. How this works is that steps are internally wrapped inside form element which has a submit handler provided by react-hook-form. You can configure validation, validation strategy and initial values for each step and connect to RHF with useFormContext in your step components. Form submission is handled by the wizard automatically but that is customizable to some extent as well.

Library basically consists of Wizard component that requires a list of step configuration objects. Wizard itself collects inputted form values from each step. Collected values can be accessed using useWizard hook.

NOTE: This project is based on the original Formik version of this same wizard. As it's almost a direct copy of the original project, there may be some hiccups or how this wizard is used may not feel the "react-hook-form way" but things are subject to change based on the feedback.

Demo

https://kennyhei.github.io/rhf-wizard/

Small demo showcasing the wizard and various use cases such as:

  • Step with basic form validation
  • Step with form where validation is dependent on what user inputted in previous view
  • Skippable step
  • Async step
  • Final step with summary of inputted data
  • Shared footer with navigation buttons
  • Shared header with progress bar
  • Animated steps
  • URL hashes

You can find more information on usage below.

Table of Contents

Installation

NPM:

npm install rhf-wizard --save

Yarn:

yarn add rhf-wizard react-hook-form

Note: It is also recommended to install yup for validation (it is used in examples below). You can install yup with npm install yup --save or yarn add yup. However, it's not mandatory as you can write your validation function to steps with plain JS as well (see validate).

Quickstart

import React from "react";
import ReactDOM from "react-dom/client";
import { Wizard, useWizard, BasicFooter } from "rhf-wizard";
import { useFormContext } from "react-hook-form";
import * as Yup from "yup";

function StepName() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <div>
      <label htmlFor="name">Name</label>
      <input {...register("name")} />
      {errors.name && <span>{errors.name.message}</span>}
    </div>
  );
}

function StepAge() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <div>
      <label htmlFor="age">Age</label>
      <input type="number" min="0" max="125" {...register("age")} />
      {errors.age && <span>{errors.age.message}</span>}
    </div>
  );
}

function StepFinal() {
  const { values } = useWizard();
  const name = values.StepName.name;
  const age = values.StepAge.age;
  return (
    <div>
      <h1>You did it!</h1>
      <p>
        Your name is {name} and your age is {age}.
      </p>
      <p>Press button to complete the wizard.</p>
      <button type="submit">Finish</button>
    </div>
  );
}

function App() {
  const steps = [
    {
      id: "StepName",
      component: <StepName />,
      initialValues: { name: "John" },
      validationSchema: Yup.object({
        name: Yup.string().required("Name is required"),
      }),
      hidePrevious: true,
    },
    {
      id: "StepAge",
      component: <StepAge />,
      initialValues: { age: 30 },
      validationSchema: Yup.object({
        age: Yup.number().positive().required("Age is required"),
      }),
    },
    {
      id: "StepFinal",
      component: <StepFinal />,
      hideNext: true,
    },
  ];

  return (
    <Wizard
      steps={steps}
      onStepChanged={(fromStep, toStep, wizardValues) => {
        console.log("step changed", fromStep.id, toStep.id, wizardValues);
      }}
      onCompleted={(values) => {
        alert("wizard completed");
        console.log("wizard completed", values);
      }}
      footer={<BasicFooter />}
    />
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);

How it works

You can follow this tutorial step by step. Full code is shown in the end.

1. Configuring list of steps

Wizard component requires a list of steps. Here's a basic example what the list looks like:

const steps = [
  {
    id: "StepName",
    component: <StepName />,
    initialValues: { name: "" },
    validationSchema: Yup.object({
      name: Yup.string().required("Name is required"),
    }),
  },
  {
    id: "StepAge",
    component: <StepAge />,
    initialValues: { age: "" },
    validationSchema: Yup.object({
      age: Yup.number().positive().required("Age is required"),
    }),
  },
  {
    id: "StepFinal",
    component: <StepFinal />,
  },
];

Each step object defines component that is used to render the step. Wizard wraps the currently active step inside <FormProvider><form></form></FormProvider>. The following step object attributes are passed automatically to react-hook-form:

  • initialValues: Default values of the react-hook-form registered fields that are rendered in the component.
  • validationSchema / validate: Either define Yup schema or function that validates the form fields.
  • validateOnChange: Tells RHF to validate the form on each input's onChange event. Defaults to true.
  • validateOnBlur: Tells RHF to validate the form on each input's onBlur event. Defaults to true.

Usually you would need to define at least initialValues and validationSchema / validate (presuming that your step component contains form fields).

You can view the full list of step options here.

2. Writing your step component(s)

If you plan to have form fields in your step component, you should use RHF's register method to render fields and formState.errors to render errors. For instance, this is what StepName and StepAge could look like:

import { useWizard } from "rhf-wizard";
import { useFormContext } from "react-hook-form";

function StepName() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <div>
      <div>
        <label htmlFor="name">Name</label>
        <input {...register("name")} />
        {errors.name && <span>{errors.name.message}</span>}
      </div>
      <button type="submit">Next</button>
    </div>
  );
}

function StepAge() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  const { goToPreviousStep } = useWizard();
  return (
    <div>
      <div>
        <label htmlFor="age">Name</label>
        <input type="number" {...register("age")} />
        {errors.age && <span>{errors.age.message}</span>}
      </div>
      <button type="button" onClick={goToPreviousStep}>
        Previous
      </button>
      <button type="submit">Next</button>
    </div>
  );
}

Note that we retrieve goToPreviousStep utility function from useWizard hook. You can read more about it here.

3. Setting up the Wizard

Now you can pass the list to Wizard:

<Wizard
  steps={steps}
  onStepChanged={(fromStep, toStep, wizardValues) => {
    console.log("step changed", fromStep.id, toStep.id, wizardValues);
  }}
  onCompleted={(values) => {
    alert("wizard completed");
    console.log("wizard completed", values);
  }}
/>
  • onStepChanged is called each time step is changed. fromStep and toStep represent step objects. wizardValues is a collection of all inputted values in previously completed steps.
  • onCompleted is called when user completes the wizard (i.e. submits last step). values contains the field-value pairs from all steps.

If we look at Quickstart example, then onStepChanged would print this after user has completed first step:

> step changed, StepName, StepAge, { StepName: { firstName: 'John', lastName: 'Doe' } }

After second step:

> step changed, StepAge, StepFinal, { StepName: { firstName: 'John', lastName: 'Doe' }, { StepAge: { age: 30 } } }

After user has completed the wizard, onCompleted would print this:

> wizard completed, { firstName: 'John', lastName: 'Doe', age: 30 }

4. Full example

Here's what the finished code looks like:

import React from "react";
import { useWizard, Wizard } from "rhf-wizard";
import { useFormContext } from "react-hook-form";
import * as Yup from "yup";

function StepName() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <div>
      <div>
        <label htmlFor="name">Name</label>
        <input {...register("name")} />
        {errors.name && <span>{errors.name.message}</span>}
      </div>
      <button type="submit">Next</button>
    </div>
  );
}

function StepAge() {
  const { goToPreviousStep } = useWizard();
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <div>
      <div>
        <label htmlFor="age">Age</label>
        <input type="number" min="0" {...register("age")} />
        {errors.age && <span>{errors.age.message}</span>}
      </div>
      <button type="button" onClick={goToPreviousStep}>
        Previous
      </button>
      <button type="submit">Next</button>
    </div>
  );
}

function StepFinal() {
  const { values, goToPreviousStep } = useWizard();
  return (
    <div>
      <p>
        Your name is {values.StepName.name} and your age is {values.StepAge.age}
      </p>
      <button type="button" onClick={goToPreviousStep}>
        Previous
      </button>
      <button type="submit">Finish</button>
    </div>
  );
}

function App() {
  const steps = [
    {
      id: "StepName",
      component: <StepName />,
      initialValues: { name: "" },
      validationSchema: Yup.object({
        name: Yup.string().required("Name is required"),
      }),
    },
    {
      id: "StepAge",
      component: <StepAge />,
      initialValues: { age: "" },
      validationSchema: Yup.object({
        age: Yup.number().positive().required("Age is required"),
      }),
    },
    {
      id: "StepFinal",
      component: <StepFinal />,
    },
  ];

  return (
    <Wizard
      steps={steps}
      onStepChanged={(fromStep, toStep, wizardValues) => {
        console.log("step changed", fromStep.id, toStep.id, wizardValues);
      }}
      onCompleted={(values) => {
        alert("wizard completed");
        console.log("wizard completed", values);
      }}
    />
  );
}

What if I don't want to use RHF in step component?

Then you can omit initialValues and validationSchema / validate attributes from the step object and not just use RHF's useFormContext hook in your step component. You can basically write anything in your component as long as it has a button of type submit for proceeding to the next step or alternatively you can use shared navigation component which will be explained next.

Navigation

If you wish to include a shared navigation for all steps in your wizard, you can create your own component and pass it to Wizard using footer or header props. When you do that, the following properties would be useful to retrieve from Wizard using useWizard hook:

  • goToPreviousStep: Function that goes to previous step.
  • isFirstStep: Boolean indicating whether step is first step.
  • isLastStep: Boolean indicating whether step is last step.
  • isLoading: Boolean indicating whether step is in loading state.
  • activeStep:
    • hidePrevious: Boolean indicating whether "Previous" button should be hidden.
    • disablePrevious: Boolean indicating whether "Previous" button should be disabled.
    • hideNext: Boolean indicating whether "Next" button should be hidden.
    • disableNext: Boolean indicating whether "Next" button should be disabled.
    • disableNextOnErrors: Boolean indicating whether "Next" button should be disabled if form has errors.

Here's an example of simple navigation component:

import React from "react";
import { Wizard, useWizard } from "rhf-wizard";
import { useFormContext } from "react-hook-form";

const steps = [
  { id: "Step1", component: <h1>Step 1</h1>, hidePrevious: true },
  { id: "Step2", component: <h1>Step 2</h1>, hideNext: true },
];

function Navigation() {
  const {
    goToPreviousStep,
    isFirstStep,
    isLastStep,
    isLoading,
    activeStep: {
      hidePrevious,
      disablePrevious,
      hideNext,
      disableNext,
      disableNextOnErrors,
    },
  } = useWizard();
  const {
    formState: { isValid },
    trigger,
  } = useFormContext();
  const isPreviousDisabled = disablePrevious || isLoading;
  const isNextDisabled =
    disableNext || isLoading || (disableNextOnErrors && !isValid);

  if (isLoading) {
    return "Loading...";
  }

  return (
    <div>
      {!hidePrevious && (
        <button
          type="button"
          onClick={goToPreviousStep}
          disabled={isPreviousDisabled}
        >
          Previous
        </button>
      )}
      {!hideNext && (
        <div
          onClick={
            isNextDisabled
              ? () => {
                  trigger();
                }
              : undefined
          }
        >
          <button type="submit" disabled={isNextDisabled}>
            Next
          </button>
        </div>
      )}
    </div>
  );
}

function App() {
  return <Wizard steps={steps} footer={<Navigation />} />;
}

BasicFooter

If you don't wish to create your own navigation component, a basic footer with navigation buttons is included in rhf-wizard. Here's how you can use it:

import React from "react";
import { Wizard, BasicFooter } from "rhf-wizard";

const steps = [
  { id: "Step1", component: <h1>Step 1</h1>, hidePrevious: true },
  { id: "Step2", component: <h1>Step 2</h1>, hideNext: true },
];

function App() {
  return (
    <Wizard
      steps={steps}
      footer={<BasicFooter textNext="Proceed" textPrevious="Go back" />}
    />
  );
}

Note that BasicFooter has two additional props textNext and textPrevious for customizing button texts. By default they are "Next" and "Previous".

You can customize looks of the basic footer by defining CSS for classes .navigation and .navigation-wrapper.

API

Step object

List of step objects that are passed to Wizard have various options you can set. Here's the full specification:

| Name | Type | Default | Description | | ---------------------- | --------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | | (Required) Unique ID for step component. Form values inputted by user are stored in the Wizard's values object under step ID key. E.g. { [id]: { [field]: [value] } }. Values can be retrieved from Wizard using useWizard hook. | | component | ReactElement | | Component used for rendering step content. Usually this is always required unless you implement default component object like in the demo/ code. | | initialValues | object | {} | Contains key-value pairs for step's form fields.Example:initialValues: { name: '', email: '' } | | hideNext | boolean | false | Indicates whether to hide submit button. | | hidePrevious | boolean | false | Indicates whether to hide "Previous" button. | | disableNext | boolean | false | Indicates whether to disable submit button. | | disableNextOnErrors | boolean | false | Indicates whether to disable submit button when form has errors. | | disablePrevious | boolean | false | Indicates whether to disable "Previous" button. | | keepValuesOnPrevious | boolean | true | Remembers inputted values in current step if user decides to navigate back to previous step without submitting the form. | | shouldSkip | async (allValues, direction) => stepValues | | Function that returns boolean telling whether the step should be skipped or not.Params:- allValues: all form field values from previous steps- direction: Tells whether user came to the current step by pressing "Previous" (-1) or by pressing "Next" (1) | | onSubmit | async (stepValues, allValues) => stepValues | | Function that serves as a custom submit handler where you can do things after successful form submission. You should return stepValues.Params:- stepValues: form field values filled in current step- allValues: all form field values from previous steps | | validate | (stepValues, allValues) => object | | Validate the form's values with a function. If there are errors, return object containing field's name as key and error message as value.Params:- stepValues: form field values filled in current step- allValues: all form field values from previous steps | | validationSchema | Yup.object | | A Yup schema. This is used for validation. Errors are mapped by key to the inner component's errors. Its keys should match of those values. Example here: https://react-hook-form.com/get-started#SchemaValidation | | validateOnBlur | boolean | true | Use this option to tell RHF to run validations on blur events. | | validateOnChange | boolean | true | Use this option to tell RHF to run validations on change events. |

Wizard

Wizard controls the step flow. Only required props is steps which contains a list of step config objects. You can customize the wizard by passing custom components using header, wrapper and footer props.

| Name | Type | Description | | --------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | steps | list | (Required) List of step objects. See example and configuration instructions above. | | onStepChanged | (fromStep, toStep, wizardValues) => void | Function that is called when user either submits currently active step or navigates back to previous step. | | onCompleted | (values) => void | Function that is called when user completes the wizard. | | header | ReactElement | Header that is shown above the active step. | | wrapper | ReactElement | Wrapper component that is wrapped around step.component. Useful if you need e.g. styling that is shared across all step components. | | footer | ReactElement | Footer that is shown below the active step. | | enableHash | boolean | Enables URL hashes per step. Hash matches with slugified step id (e.g. StepName -> #step-name) and is updated accordingly every time when step is changed. |

useWizard

Wizard hook that you can use in any step component or in custom header, wrapper and footer component. Used to retrieve info and utility functions related to wizard.

| Name | Type | Description | | ------------------ | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | values | object | Contains all form field values from previously completed steps. Example:{ StepName: { firstName: 'John', lastName: 'Doe' }, StepAge: { age: 30 } | | activeStep | object | Active step object. | | stepNumber | number | Order number of currently active step. Indexing starts from 1. | | totalSteps | number | Number of total steps. | | isLoading | boolean | Is step in loading state or not. Set to true when step.onSubmit handler is called and back to false when executing handler is done. | | isFirstStep | boolean | Is the currently active step the first step. | | isLastStep | boolean | Is the currently active step the last step. | | updateStep | (key: string, value: any) => void | Function used to set the step object's [key] attribute to value. Mostly used to manually hide or disable navigational buttons (e.g. hideNext / disableNext). | | setIsLoading | (truthy: boolean) => void | Function used to set the value of isLoading attribute. | | goToPreviousStep | () => void | Go to previous step. | | goToStep | (index: number) => void | Go to step at the specified index. |

Advanced topics

For more advanced use of the wizard I recommend playing with demo and reading code in demo/ folder. Some topics are covered below:

Skipping steps

You can define a shouldSkip function that returns boolean value in the step object. If true, step is skipped and wizard moves on the next step (or previous step, depending on if user pressed "Previous" button or "Next" button).

shouldSkip receives two parameters:

  • values: All values that were inputted in previous steps
  • direction: Either -1 or 1, indicating whether we are moving backwards or forwards in the wizard.

Example:

import { Wizard, useWizard } from "rhf-wizard";
import { useFormContext } from "react-hook-form";

function NonSkippableStep() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <div>
      <p>This is non-skippable step.</p>
      <p>Type "skip" if you want to skip next step.</p>
      <div>
        <input {...register("text")} />
        {errors.text && <span>{errors.text.message}</span>}
      </div>
      <button type="submit">Next</button>
    </div>
  );
}

function SkippableStep() {
  const { goToPreviousStep } = useWizard();
  return (
    <div>
      <p>This is skipped if user typed "skip" in previous step.</p>
      <button type="button" onClick={goToPreviousStep}>
        Previous
      </button>
      <button type="submit">Next</button>
    </div>
  );
}

function FinalStep() {
  const { goToPreviousStep } = useWizard();
  return (
    <div>
      <h1>You did it!</h1>
      <button type="button" onClick={goToPreviousStep}>
        Previous
      </button>
    </div>
  );
}

const steps = [
  {
    id: "NonSkippableStep",
    component: <NonSkippableStep />,
    initialValues: { text: "" },
  },
  {
    id: "SkippableStep",
    component: <SkippableStep />,
    shouldSkip: (values, direction) => {
      return values.NonSkippableStep.text === "skip";
    },
  },
  {
    id: "FinalStep",
    component: <FinalStep />,
  },
];

function App() {
  return <Wizard steps={steps} />;
}

Validating form field based on what user inputted in previous step(s)

Sometimes there's a need to validate field based on what user inputted in previous steps. In this case, you can define a validate function to the step object.

validate receives following parameters:

  • stepValues: form field values filled in current step
  • allValues: all form field values from previous steps

If there are errors, return object containing field's name as key and error message as value.

Example:

function StepName() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <div>
      <div>
        <label htmlFor="name">Name</label>
        <input {...register("name")} />
        {errors.name && <span>{errors.name.message}</span>}
      </div>
      <button type="submit">Next</button>
    </div>
  );
}

function StepFullName() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  const { goToPreviousStep } = useWizard();
  return (
    <div>
      <div>
        <label htmlFor="fullName">Full Name</label>
        <input {...register("fullName")} />
        {errors.fullName && <span>{errors.fullName.message}</span>}
      </div>
      <button type="button" onClick={goToPreviousStep}>
        Previous
      </button>
    </div>
  );
}

const steps = [
  {
    id: "StepName",
    component: <StepName />,
    initialValues: {
      name: "",
    },
  },
  {
    id: "StepFullName",
    component: <StepFullName />,
    initialValues: {
      fullName: "",
    },
    validate: (stepValues, allValues) => {
      const errors = {};
      if (!stepValues.fullName.includes(allValues.StepName.name)) {
        errors.fullName = "Full name is missing your first name.";
      }
      return errors;
    },
  },
];

Saving form values in sessionStorage

If you want form fields to persist e.g. between page refreshes, you can use sessionStorage to your advantage. rhf-wizard doesn't offer solution out of the box but here's one way you can implement the logic:

function getStepInitialValues(stepId) {
  const values = JSON.parse(sessionStorage.getItem("wizardValues"));
  return values?.[stepId];
}

function StepName() {
  const {
    register,
    formState: { errors },
  } = useFormContext();
  return (
    <div>
      <label htmlFor="name">Name</label>
      <input {...register("name")} />
      {errors.name && <span>{errors.name.message}</span>}
    </div>
  );
}

const steps = [
  {
    id: "StepName",
    component: <StepName />,
    initialValues: getStepInitialValues("StepName") || {
      name: "",
    },
  },
  {
    id: "StepInstructions",
    component: (
      <p>
        Refresh page to see that value of "Name" input is prefilled from session
        storage.
      </p>
    ),
  },
];

return (
  <Wizard
    steps={steps}
    onStepChanged={(fromStep, toStep, wizardValues) => {
      sessionStorage.setItem("wizardValues", JSON.stringify(wizardValues));
    }}
    footer={<BasicFooter />}
  />
);

Using URL hashes in the wizard

When initiating Wizard, you can enable URL hashes with prop enableHash:

<Wizard
  enableHash
  steps={[
    { id: "StepName", component: <div>Name</div> },
    { id: "StepAge", component: <div>Age</div> },
  ]}
/>

Step IDs are converted into URL-friendly hashes. When user is in the first step, URL will be updated with hash /#step-name and when user proceeds to the next step, URL is updated with hash /#step-age. When refreshing page, active step will be chosen based on URL hash. If there is no hash or hash doesn't match with any step, first step is shown.

Animations

Check demo/components/WizardStepWrapper.jsx on how to use framer-motion in your steps.

Example:

import { BasicFooter, Wizard, useWizard } from "rhf-wizard";
import { useFormContext } from "react-hook-form";
import { AnimatePresence, motion } from "framer-motion";

function Field({ name, type }) {
  const { register } = useFormContext();
  return (
    <div>
      <label htmlFor={name}>{name}</label>
      <input type={type} {...register(name)} />
    </div>
  );
}

const steps = [
  {
    id: "Step1",
    component: <Field name="name" type="text" />,
    initialValues: { name: "" },
  },
  {
    id: "Step2",
    component: <Field name="age" type="number" />,
    initialValues: { age: "" },
  },
];

function Wrapper() {
  const { activeStep } = useWizard();
  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={activeStep.id}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        transition={{ duration: 0.5 }}
      >
        {activeStep.component}
      </motion.div>
    </AnimatePresence>
  );
}

return <Wizard steps={steps} wrapper={<Wrapper />} footer={<BasicFooter />} />;

Creating wrapper for steps

You can wrap steps by defining your own wrapper component. Here's an example:

import { useWizard, Wizard } from "rhf-wizard";

function StepWrapper() {
  const { activeStep } = useWizard();
  return (
    <div style={{ backgroundColor: "lightblue" }}>
      <h1>Wrapper</h1>
      {activeStep.component}
    </div>
  );
}

const steps = [{ id: "Step", component: <p>I'm a simple step.</p> }];

function App() {
  return <Wizard steps={steps} wrapper={<StepWrapper />} />;
}

Creating header for wizard

You can create header for wizard by defining your own header component. Here's an example:

import { useWizard, Wizard } from "rhf-wizard";

function Header() {
  const { activeStep } = useWizard();
  return (
    <div style={{ padding: "2rem 0", background: "yellow" }}>
      <h1 style={{ textAlign: "center" }}>Header of #{activeStep.id}</h1>
    </div>
  );
}

const steps = [{ id: "Step", component: <p>I'm a simple step.</p> }];

function App() {
  return <Wizard steps={steps} header={<Header />} />;
}

Creating custom submit handler for step

You can write custom submit handler for step in attribute onSubmit which is a function. onSubmit receives three parameters:

  • stepValues: Form field values filled in current step
  • allValues: All form field values from previous steps

One use case would be to save the inputted values in backend so that user won't have to start the wizard over if e.g. page is refreshed:

const steps = [
  {
    id: "StepName",
    component: <StepName />,
    onSubmit: async (stepValues, allValues) => {
      await fetch(someUrl, {
        method: "POST",
        body: JSON.stringify({ id: "StepName", data: stepValues }),
      });
      return stepValues;
    },
  },
];

Note that when onSubmit is called, Wizard sets isLoading to true and when executing onSubmit is done, isLoading is set back to false.