Stepper Form using react-hook-form and yup

Search for a command to run...

No comments yet. Be the first to comment.
Day 1,2,3 East peasy for now. Day 4 type Address = { address: string; city: string }; type PresentDeliveryList<T> = Record<keyof T, Address>; Learnt that we can make object type using Record and key of. Day 5 type SantasList<A extends readonly any[]...
These tips are from my own experience. Add a backup method. I currently use a program called rclone to backup my stuff. I didn't want to install multiple programs for each cloud provider like Drive, Dropbox, also there is not much support in case of...
This tutorial assume basic knowledge of react-hook-form, check out their helpful tutorials first here if you are new. https://react-hook-form.com/get-started/ Let's take an example of a form where you need to upload files. First we setup react-hook-f...

I'll show you how to switch between git accounts on your machine. You might have a different git account for work and personal use, switching between those accounts can be time-consuming and prone to error. It's also pretty boring to do. You need to ...

Recently, I had to create to a stepper form for a website.
A stepper form looks like this -
source - https://material.angular.io/components/stepper/overview
To create this using react-hook-form, follow these steps.
npm i react-hook-form react-form-stepper @hookform/resolvers yup
Create Stepper.js
Import dependencies.
import { FormOne, FormThree, FormTwoSelf, FormTwoOthers } from "./Forms";
import { Stepper } from "react-form-stepper";
import { useEffect, useRef, useState } from "react";
import { Button } from "react-bootstrap";
import { Loader } from "../../components/Loader";
This will import the stepper components, the forms itself and some react hooks.
const steps = ["One", "Two", "Three"];
const onSubmit = (data) => {
console.log(data);
// add necessary logic like POSTing to API or something.
};
// self is a variable, we can use any type of conditions :D
// I'll tell how to create the forms in next steps.
function getStepContent(step, formContent, handleNext, ref, self) {
switch (step) {
case 0:
return <FormOne {...{ handleNext }} _ref={ref} />;
case 1:
if (self) return <FormTwoSelf {...{ handleNext }} _ref={ref} />;
else return <FormTwoOthers {...{ handleNext }} _ref={ref} />;
case 2:
return <FormThree {...{ formContent, handleNext }} _ref={ref} />;
}
}
export const FormStepper = () => {
const [activeStep, setActiveStep] = useState(0); // store which step is the form at.
const [compiledForm, setCompiledForm] = useState({}); // store the whole form data
const ref = useRef(null);
const [self, setSelf] = useState(true);
const [loading, setLoading] = useState(false);
useEffect(() => console.log("compiledForm", compiledForm), [compiledForm]);
useEffect(() => {
if (activeStep === steps.length) {
setLoading(true);
const data = Object.values(compiledForm).reduce(
(prev, curr) => ({
...prev,
...curr,
}),
{}
);
// the compiledForm is in form of {0: form_data, 1: form_data, .... } hence using the reducer
console.log(data);
// show a loader while POSTing
// onSubmit(data);
setLoading(false);
}
}, [activeStep]);
const handleNext = (data) => {
setCompiledForm({ ...compiledForm, [activeStep]: data });
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handleBack = () => {
if (activeStep > 0) {
setCompiledForm({ ...compiledForm, [activeStep]: {} });
setActiveStep((prevActiveStep) => prevActiveStep - 1);
}
};
return (
<div>
<Stepper
styleConfig={{
activeBgColor: "#3a936c",
completedBgColor: "#146240",
fontWeight: "bold",
}}
steps={steps.map((step) => ({
label: "Step " + step,
}))}
activeStep={activeStep}
/>
<div>
{activeStep === steps.length ? (
<div className="flex flex-col justify-center">
{loading ? (
<Loader />
) : (
<div className="text-center">Completed</div>
)}
<Button variant="outline-success" onClick={() => {}}>
Close
</Button>
</div>
) : (
<div>
{getStepContent(
activeStep,
compiledForm,
handleNext,
ref,
self
)}
<div className="flex gap-2 justify-center">
<Button
variant="outline-danger"
onClick={handleBack}
disabled={activeStep === 0}
>
Back
</Button>
// the next button will click on hidden submit button hence allowing the validation to take place.
<Button
type="submit"
variant="outline-success"
onClick={() => ref.current.click()}
>
{activeStep === steps.length - 1
? "Finish"
: "Next"}
</Button>
</div>
</div>
)}
</div>
</div>
);
};
Forms.js. We can import necessary dependencies and schemas for validation.import { Controller, FormProvider, useForm } from "react-hook-form";
import {
fundraiserTypeOptions,
GradeOptions,
fundraisingTypeOptions,
formOneSchema,
} from "./schemas";
import { yupResolver } from "@hookform/resolvers/yup";
import { isAuthenticatedSelector } from "../../redux/selectors";
import { useSelector } from "react-redux";
export const FormOne = ({ handleNext, _ref }) => {
const isAuthenticated = useSelector(isAuthenticatedSelector);
const methods = useForm({
resolver: yupResolver(formOneSchema[!!isAuthenticated]),
});
const { handleSubmit } = methods;
return (
<FormProvider {...methods}>
<form
onSubmit={handleSubmit(handleNext)}
className="flex flex-col gap-4 my-4"
>
<RenderSelect
inputName="fundraisingType"
options={fundraisingTypeOptions}
placeholder="Fundraising Type"
/>
<RenderSelect
inputName="fundraiserType"
options={fundraiserTypeOptions}
placeholder="You are raising funds for.."
/>
{!isAuthenticated ? (
<>
<InputRender
inputName="Tel"
label="+91"
props={{
type: "tel",
placeholder: "Mobile Number",
}}
/>
<div className="flex gap-2">
<InputRender
inputName="FirstName"
props={{
type: "text",
placeholder: "First Name",
autoComplete: "given-name",
}}
/>
<InputRender
inputName="LastName"
props={{
type: "text",
placeholder: "Last Name",
autoComplete: "family-name",
}}
/>
</div>
<InputRender
props={{
type: "text",
placeholder: "Address",
autoComplete: "street-address",
}}
inputName="StreetAddress"
/>
<InputRender
props={{
type: "text",
placeholder: "City",
autoComplete: "address-level2",
}}
inputName="City"
/>
<RenderSelect
inputName="State"
options={StatesOptions}
placeholder="State"
/>
</>
) : null}
// this is the important part as clicking on next will actually submit the form using this hidden button
<button type="submit" hidden={true} ref={_ref}></button>
</form>
</FormProvider>
);
};
As seen above the hidden button allows the submission, this has to be used in each form. Also make sure the field names in each form is unique so that they are not over written after the form is finished.
I also used tailwind and bootstrap button in this project.
References: