javascript 65 lines · 9 steps

Building a multi-step form wizard hook in React

A custom React hook that tracks step position, per-step validation, and form data behind one clean interface.

Explained by highlit
1import { useState } from 'react';
2 
3const steps = ['account', 'profile', 'review'];
4 
5const validators = {
6 account: (data) => {
7 const errors = {};
8 if (!/^[^@]+@[^@]+\.[^@]+$/.test(data.email)) errors.email = 'Enter a valid email';
9 if (data.password.length < 8) errors.password = 'At least 8 characters';
10 return errors;
11 },
12 profile: (data) => {
13 const errors = {};
14 if (!data.fullName.trim()) errors.fullName = 'Name is required';
15 if (!/^\+?\d{7,15}$/.test(data.phone)) errors.phone = 'Enter a valid phone number';
16 return errors;
17 },
18 review: () => ({}),
19};
20 
21export function useWizard(initialData, onComplete) {
22 const [stepIndex, setStepIndex] = useState(0);
23 const [data, setData] = useState(initialData);
24 const [errors, setErrors] = useState({});
25 
26 const step = steps[stepIndex];
27 
28 const setField = (name, value) => {
29 setData((prev) => ({ ...prev, [name]: value }));
30 setErrors((prev) => {
31 const { [name]: _, ...rest } = prev;
32 return rest;
33 });
34 };
35 
36 const validateCurrent = () => {
37 const found = validators[step](data);
38 setErrors(found);
39 return Object.keys(found).length === 0;
40 };
41 
42 const next = () => {
43 if (!validateCurrent()) return;
44 if (stepIndex === steps.length - 1) {
45 onComplete(data);
46 } else {
47 setStepIndex((i) => i + 1);
48 }
49 };
50 
51 const back = () => setStepIndex((i) => Math.max(0, i - 1));
52 
53 return {
54 step,
55 stepIndex,
56 data,
57 errors,
58 setField,
59 next,
60 back,
61 isFirst: stepIndex === 0,
62 isLast: stepIndex === steps.length - 1,
63 progress: (stepIndex + 1) / steps.length,
64 };
65}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Keying validators by step name lets you drive per-page rules from a plain lookup object instead of branching logic.
  2. 2Returning a single object of state plus actions gives components a clean, self-contained API to render any step.
  3. 3Advancing only after validation passes keeps invalid data from ever moving forward in the flow.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a multi-step form wizard hook in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code