Back to roadmaps zustand Course

Project: Multi-Step Wizard Form

In this project, we will construct a multi-step user registration form. The state, current progress step, and input fields values are stored inside a central Zustand store, allowing users to navigate back and forth without losing inputted details.


1. Wizard Specifications

  • Step 1: Account Information: Username and password inputs.
  • Step 2: Profile Details: First name, last name, and contact email.
  • Step 3: Summary and Submission: Review all fields and execute submission action.

2. Implementing the Wizard Store

Create a store file named useFormStore.ts inside src/stores/:

// src/stores/useFormStore.ts
import { create } from "zustand";

interface FormData {
  username: string;
  email: string;
  fullName: string;
}

interface FormState {
  currentStep: number;
  data: FormData;
  nextStep: () => void;
  prevStep: () => void;
  updateFields: (fields: Partial<FormData>) => void;
  resetForm: () => void;
}

export const useFormStore = create<FormState>((set) => ({
  currentStep: 1,
  data: {
    username: "",
    email: "",
    fullName: "",
  },

  nextStep: () => set((state) => ({ currentStep: state.currentStep + 1 })),
  prevStep: () => set((state) => ({ currentStep: Math.max(1, state.currentStep - 1) })),
  
  updateFields: (fields) => set((state) => ({
    data: { ...state.data, ...fields }
  })),
  
  resetForm: () => set({
    currentStep: 1,
    data: { username: "", email: "", fullName: "" }
  }),
}));

3. Implementing the Form Component

Here is the React wizard form markup layout:

// src/components/RegistrationWizard.tsx
import { useFormStore } from "../stores/useFormStore";

export default function RegistrationWizard() {
  const { currentStep, data, nextStep, prevStep, updateFields, resetForm } = useFormStore();

  const handleFormSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log("Submitting registration profile data:", data);
    alert("Registration successfully completed!");
    resetForm();
  };

  return (
    <div className="max-w-md mx-auto p-8 border rounded-xl bg-white shadow-sm mt-12">
      <div className="flex justify-between text-sm text-gray-500 mb-6">
        <span>Step {currentStep} of 3</span>
        <span className="font-semibold">Progress: {Math.round((currentStep / 3) * 100)}%</span>
      </div>

      <form onSubmit={handleFormSubmit}>
        {/* Step 1: Account Setup */}
        {currentStep === 1 && (
          <div className="space-y-4">
            <h2 className="text-xl font-bold">Account Setup</h2>
            <input
              type="text"
              value={data.username}
              onChange={(e) => updateFields({ username: e.target.value })}
              placeholder="Username"
              className="w-full border p-3 rounded-lg"
              required
            />
          </div>
        )}

        {/* Step 2: Personal Profile Details */}
        {currentStep === 2 && (
          <div className="space-y-4">
            <h2 className="text-xl font-bold">Personal Profile</h2>
            <input
              type="text"
              value={data.fullName}
              onChange={(e) => updateFields({ fullName: e.target.value })}
              placeholder="Full Name"
              className="w-full border p-3 rounded-lg"
              required
            />
            <input
              type="email"
              value={data.email}
              onChange={(e) => updateFields({ email: e.target.value })}
              placeholder="Email Address"
              className="w-full border p-3 rounded-lg"
              required
            />
          </div>
        )}

        {/* Step 3: Review Confirmation */}
        {currentStep === 3 && (
          <div className="space-y-4">
            <h2 className="text-xl font-bold">Review Details</h2>
            <div className="p-4 bg-gray-50 rounded-lg space-y-2 text-sm">
              <p><strong>Username:</strong> {data.username}</p>
              <p><strong>Full Name:</strong> {data.fullName}</p>
              <p><strong>Email:</strong> {data.email}</p>
            </div>
          </div>
        )}

        {/* Action controls buttons */}
        <div className="flex justify-between mt-8">
          {currentStep > 1 && (
            <button type="button" onClick={prevStep} className="border px-4 py-2 rounded-lg">
              Back
            </button>
          )}
          {currentStep < 3 ? (
            <button type="button" onClick={nextStep} className="bg-blue-600 text-white px-6 py-2 rounded-lg ml-auto">
              Next Step
            </button>
          ) : (
            <button type="submit" className="bg-green-600 text-white px-6 py-2 rounded-lg ml-auto">
              Submit registration
            </button>
          )}
        </div>
      </form>
    </div>
  );
}
Published on Last updated: