Last active
October 5, 2022 14:57
-
-
Save Clarity-89/73aba7ce1f714ad7172d32098c0e10bf to your computer and use it in GitHub Desktop.
Building Multistep Forms With React Hook Form
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// state.js | |
import React, { createContext, useContext, useState } from "react"; | |
export const AppStateContext = createContext(); | |
export function AppProvider({ children }) { | |
const value = useState({}); | |
return ( | |
<AppStateContext.Provider value={value}> | |
{children} | |
</AppStateContext.Provider> | |
); | |
} | |
export function useAppState() { | |
const context = useContext(AppStateContext); | |
if (!context) { | |
throw new Error("useAppState must be used within the AppProvider"); | |
} | |
return context; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; | |
import { AppProvider } from "./state"; | |
import { Contact } from "./Steps/Contact"; | |
import { Education } from "./Steps/Education"; | |
import { About } from "./Steps/About"; | |
import { Confirm } from "./Steps/Confirm"; | |
import "./styles.scss"; | |
export const App = () => { | |
return ( | |
<AppProvider> | |
<Router> | |
<Routes> | |
<Route path="/" element={<Contact />} /> | |
<Route path="/education" element={<Education />} /> | |
<Route path="/about" element={<About />} /> | |
<Route path="/confirm" element={<Confirm />} /> | |
</Routes> | |
</Router> | |
</AppProvider> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
export const Field = ({ children, label, error }) => { | |
const id = getChildId(children); | |
return ( | |
<div className="col-sm-12 mb-3"> | |
<label htmlFor={id} className="form-label"> | |
{label} | |
</label> | |
{children} | |
{error && <small className="error">{error.message}</small>} | |
</div> | |
); | |
}; | |
// Get id prop from a child element | |
export const getChildId = (children) => { | |
const child = React.Children.only(children); | |
if ("id" in child?.props) { | |
return child.props.id; | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Steps/Contact.js | |
import { useForm } from "react-hook-form"; | |
import { useAppState } from "../state"; | |
import { useNavigate } from "react-router-dom"; | |
import { Button, Field, Form, Input } from "../Forms"; | |
export const Contact = () => { | |
const [state, setState] = useAppState(); | |
const { | |
handleSubmit, | |
register, | |
watch, | |
formState: { errors }, | |
} = useForm({ defaultValues: state, mode: "onSubmit" }); | |
const watchPassword = watch("password"); | |
const navigate = useNavigate(); | |
const saveData = (data) => { | |
setState({ ...state, ...data }); | |
navigate("/education"); | |
}; | |
return ( | |
<Form onSubmit={handleSubmit(saveData)}> | |
<fieldset> | |
<legend>Contact</legend> | |
<Field label="First name" error={errors?.firstName}> | |
<Input | |
{...register("firstName", { required: "First name is required" })} | |
id="first-name" | |
/> | |
</Field> | |
<Field label="Last name" error={errors?.lastName}> | |
<Input {...register("lastName")} id="last-name" /> | |
</Field> | |
<Field label="Email" error={errors?.email}> | |
<Input | |
{...register("email", { required: "Email is required" })} | |
type="email" | |
id="email" | |
/> | |
</Field> | |
<Field label="Password" error={errors?.password}> | |
<Input | |
{...register("password", { required: "Password is required" })} | |
type="password" | |
id="password" | |
/> | |
</Field> | |
<Field label="Confirm password" error={errors?.confirmPassword}> | |
<Input | |
{...register("confirmPassword", { | |
required: "Confirm the password", | |
validate: (value) => | |
value === watchPassword || "The passwords do not match", | |
})} | |
type="password" | |
id="password-confirm" | |
/> | |
</Field> | |
<Button>Next {">"}</Button> | |
</fieldset> | |
</Form> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Steps/Education.js | |
import { useForm } from "react-hook-form"; | |
import { useAppState } from "../state"; | |
import { useNavigate } from "react-router-dom"; | |
import { Button, Field, Form, Input } from "../Forms"; | |
export const Education = () => { | |
const [state, setState] = useAppState(); | |
const { handleSubmit, register } = useForm({ defaultValues: state }); | |
const navigate = useNavigate(); | |
const saveData = (data) => { | |
setState({ ...state, ...data }); | |
navigate("/about"); | |
}; | |
return ( | |
<Form onSubmit={handleSubmit(saveData)}> | |
<fieldset> | |
<legend>Education</legend> | |
<Field label="University"> | |
<Input {...register("university")} id="university" /> | |
</Field> | |
<Field label="Degree"> | |
<Input {...register("degree")} id="degree" /> | |
</Field> | |
<div className="button-row"> | |
<Button variant="secondary" onClick={() => navigate("/")}> | |
{"<"} Previous | |
</Button> | |
<Button>Next {">"}</Button> | |
</div> | |
</fieldset> | |
</Form> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Steps/About.js | |
import { useForm } from "react-hook-form"; | |
import { useAppState } from "../state"; | |
import { useNavigate } from "react-router-dom"; | |
import { Button, Field, Form } from "../Forms"; | |
export const About = () => { | |
const [state, setState] = useAppState(); | |
const { handleSubmit, register } = useForm({ defaultValues: state }); | |
const navigate = useNavigate(); | |
const saveData = (data) => { | |
setState({ ...state, ...data }); | |
navigate("/confirm"); | |
}; | |
return ( | |
<Form onSubmit={handleSubmit(saveData)}> | |
<fieldset> | |
<legend>About</legend> | |
<Field label="About me"> | |
<textarea | |
{...register("about")} | |
id="about" | |
className="form-control" | |
/> | |
</Field> | |
<div className="button-row"> | |
<Button variant="secondary" onClick={() => navigate("/education")}> | |
{"<"} Previous | |
</Button> | |
<Button>Next {">"}</Button> | |
</div> | |
</fieldset> | |
</Form> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Section.js | |
import { useNavigate } from "react-router-dom"; | |
import { Button } from "./Button"; | |
export const Section = ({ title, children, url }) => { | |
const navigate = useNavigate(); | |
return ( | |
<div className="section mb-4"> | |
<div className="title-row mb-4"> | |
<h4>{title}</h4> | |
<Button type="button" variant="secondary" onClick={() => navigate(url)}> | |
Edit | |
</Button> | |
</div> | |
<div className="content">{children}</div> | |
</div> | |
); | |
}; | |
export const SectionRow = ({ children }) => { | |
return <div className="section-row">{children}</div>; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Steps/Confirm.js | |
import { useAppState } from "../state"; | |
import { useForm } from "react-hook-form"; | |
import { Button, Form, Section, SectionRow } from "../Forms"; | |
export const Confirm = () => { | |
const [state] = useAppState(); | |
const { handleSubmit } = useForm({ defaultValues: state }); | |
const submitData = (data) => { | |
console.info(data); | |
// Submit data to the server | |
}; | |
return ( | |
<Form onSubmit={handleSubmit(submitData)}> | |
<h1 className="mb-4">Confirm</h1> | |
<Section title="Personal info" url="/"> | |
<SectionRow> | |
<div>First name</div> | |
<div>{state.firstName}</div> | |
</SectionRow> | |
<SectionRow> | |
<div>Last name</div> | |
<div>{state.lastName}</div> | |
</SectionRow> | |
<SectionRow> | |
<div>Email</div> | |
<div>{state.email}</div> | |
</SectionRow> | |
</Section> | |
<Section title="Education" url="/education"> | |
<SectionRow> | |
<div>University</div> | |
<div>{state.university}</div> | |
</SectionRow> | |
<SectionRow> | |
<div>Degree</div> | |
<div>{state.degree}</div> | |
</SectionRow> | |
</Section> | |
<Section title="About" url="/about"> | |
<SectionRow> | |
<div>About me</div> | |
<div>{state.about}</div> | |
</SectionRow> | |
</Section> | |
<div className="clo-md-12 d-flex justify-content-start"> | |
<Button>Submit</Button> | |
</div> | |
</Form> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useLocation } from "react-router-dom"; | |
export const Stepper = () => { | |
const location = useLocation(); | |
const getLinkClass = (path) => { | |
return ( | |
"nav-link disabled " + (path === location.pathname ? "active" : undefined) | |
); | |
}; | |
return ( | |
<nav className="stepper navbar navbar-expand-lg"> | |
<div className="collapse navbar-collapse"> | |
<ol className="navbar-nav"> | |
<li className="step nav-item"> | |
<span className={getLinkClass("/")}>Contact</span> | |
</li> | |
<li className="step nav-item"> | |
<span className={getLinkClass("/education")}>Education</span> | |
</li> | |
<li className="step nav-item"> | |
<span className={getLinkClass("/about")}>About</span> | |
</li> | |
<li className="step nav-item"> | |
<span className={getLinkClass("/confirm")}>Confirm</span> | |
</li> | |
</ol> | |
</div> | |
</nav> | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const App = () => { | |
return ( | |
<AppProvider> | |
<Router> | |
<Stepper /> | |
<Routes> | |
<Route path="/" element={<Contact />} /> | |
<Route path="/education" element={<Education />} /> | |
<Route path="/about" element={<About />} /> | |
<Route path="/confirm" element={<Confirm />} /> | |
</Routes> | |
</Router> | |
</AppProvider> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment