Mahamudul Hasan Rubel
HomeBlogCoursesAboutProjectsSkillsExperiencePhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • Blog
  • Courses
  • About
  • Projects
  • Skills
  • Experience
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
Lesson 31 of the Intermediate React: Hooks, State & Data Patterns course
ReactJune 26, 20263 min read

Handling Multi-step Forms: Building Robust Wizards in React

Master multi-step forms by centralizing state and managing wizard navigation. Learn to persist data across steps for a seamless dashboard configuration UX.

ReactFormsState ManagementUXWizardsjavascriptfrontend

Previously in this course, we explored schema-based validation with Zod to ensure our form inputs are type-safe and clean. Now, we're taking that foundation to the next level by architecting multi-step forms.

When building complex dashboard configurations, you rarely want to overwhelm the user with a single page containing dozens of fields. Breaking these into a "wizard" pattern improves UX, but introduces a new challenge: how do we maintain state across multiple components?

The Architecture of Wizards

In a multi-step form, the "source of truth" must live outside the individual step components. If you keep the state inside each step, you lose the data as soon as the user navigates away.

We need a strategy that:

  1. Centralizes State: A parent container (or context) holds the entire form data object.
  2. Handles Navigation: A local state tracks the current step index.
  3. Persists Progress: The parent passes down the current data and an update function to each step.

Worked Example: Building the Dashboard Config Wizard

Let's build a three-step wizard for our dashboard settings: Profile, Preferences, and Review.

JSX
import { useState } from CE9178">'react';

const Wizard = () => {
  const [step, setStep] = useState(0);
  const [formData, setFormData] = useState({
    username: CE9178">'',
    theme: CE9178">'light',
    notifications: true,
  });

  const updateData = (newData) => {
    setFormData((prev) => ({ ...prev, ...newData }));
  };

  const next = () => setStep((s) => s + 1);
  const prev = () => setStep((s) => s - 1);

  return (
    <div className="wizard">
      {step === 0 && <ProfileStep data={formData} onUpdate={updateData} />}
      {step === 1 && <PreferencesStep data={formData} onUpdate={updateData} />}
      {step === 2 && <ReviewStep data={formData} />}
      
      <div className="controls">
        {step > 0 && <button onClick={prev}>Back</button>}
        {step < 2 && <button onClick={next}>Next</button>}
        {step === 2 && <button onClick={() => console.log(formData)}>Submit</button>}
      </div>
    </div>
  );
};

By lifting the state up, we ensure that whether the user is on the first or third step, the formData object remains intact. This is a critical pattern for robust controlled vs uncontrolled components workflows.

Hands-on Exercise: Implementing Step Persistence

  1. Create a useFormState custom hook: Extract the formData and updateData logic into a hook so your Wizard component doesn't get cluttered.
  2. Add Validation: Integrate the Zod logic from our previous lesson to ensure that clicking "Next" only advances the state if the current step's data is valid.
  3. Persist to LocalStorage: Use your useLocalStorage logic to ensure that if a user refreshes the page mid-wizard, their progress is saved.

Common Pitfalls

  • Losing State on Unmount: Never define your formData inside a step component. If you move from StepA to StepB, StepA unmounts and its local state is destroyed. Always lift it to the parent.
  • Over-complicating with Context: You don't always need Context for a wizard. If the wizard exists on only one page, pass props down. Only reach for Context if you need to access this form state from deeply nested components or multiple parts of the application.
  • Ignoring the "Back" Button: Users expect to go back and see their previous inputs. Ensure your inputs are controlled components that receive their value from the central formData state.

Recap

Managing multi-step forms effectively boils down to state placement. By keeping data in a parent component, you treat the entire wizard as a single, cohesive unit of data. This prevents data loss, simplifies validation, and provides a predictable user experience for complex dashboard configurations.

Up next: We will discuss how to optimize these form submissions by handling loading states and server-side errors, ensuring your users get immediate feedback when they hit that final "Submit" button.

Previous lessonSchema-based Validation with ZodNext lesson Optimizing Form Submissions
Back to Blog

Similar Posts

ReactJune 26, 20263 min read

Optimizing Form Submissions: UX, Errors, and API Handling

Learn to optimize form submissions in React by disabling buttons during requests, handling server errors, and providing clear, actionable user feedback.

Read more
ReactJune 26, 20263 min read

Finalizing Dashboard Data Flow: Ensuring State Consistency

Master dashboard data flow by auditing state synchronization and fixing stale data issues. Learn to bridge the gap between server state and your local UI.

Part of the course

Intermediate React: Hooks, State & Data Patterns

intermediate · Lesson 31 of 48

  1. 1

    Mastering useRef for DOM Access

    4 min
  2. 2

    Persistent Mutable Values with useRef

    4 min
  3. 3

    Memoizing Expensive Calculations with useMemo

    3 min
Read more
ReactJune 26, 20263 min read

Real-time Form Validation in React: A Pro Guide

Learn to implement real-time form validation in React for instant user feedback. Master input state, error messaging, and submission toggling for better UX.

Read more
4

Optimizing Function References with useCallback

3 min
  • 5

    Introduction to Custom Hooks

    4 min
  • 6

    Building a useLocalStorage Hook

    4 min
  • 7

    Refactoring Dashboard Settings

    4 min
  • 8

    Complex State with useReducer

    3 min
  • 9

    Managing Object-Based State

    3 min
  • 10

    Introduction to Context API

    3 min
  • 11

    Architecting Global State with Context and Reducer

    3 min
  • 12

    Implementing Theme Context

    4 min
  • 13

    Structuring State for Performance

    3 min
  • 14

    Handling Authentication State

    3 min
  • 15

    Integrating Reducers with Auth State

    3 min
  • 16

    Introduction to React Router

    3 min
  • 17

    Dynamic Routing with URL Parameters

    3 min
  • 18

    Nested Routes and Layouts

    4 min
  • 19

    Protected Routes for Authenticated Views

    3 min
  • 20

    Programmatic Navigation

    3 min
  • 21

    Building the Dashboard Navigation Structure

    3 min
  • 22

    Asynchronous Data Lifecycle

    3 min
  • 23

    Caching Strategies with React Query

    4 min
  • 24

    Mutations and Data Updates

    4 min
  • 25

    Synchronizing Client and Server State

    3 min
  • 26

    Integrating Live Data into the Dashboard

    3 min
  • 27

    Error Handling and Loading UI

    3 min
  • 28

    Controlled vs Uncontrolled Components

    3 min
  • 29

    Real-time Form Validation

    3 min
  • 30

    Schema-based Validation with Zod

    3 min
  • 31

    Handling Multi-step Forms

    3 min
  • 32

    Optimizing Form Submissions

    3 min
  • 33

    Performance Profiling with React DevTools

    3 min
  • 34

    Refactoring for Scalability

    3 min
  • 35

    Finalizing Dashboard Data Flow

    3 min
  • 36

    Deploying the Application

    4 min
  • 37

    Advanced Hook Composition

    3 min
  • 38

    Implementing Middleware for State

    Coming soon
  • 39

    Advanced Context Patterns

    Coming soon
  • 40

    Router Loaders and Data Prefetching

    Coming soon
  • 41

    Complex Route Guards

    Coming soon
  • 42

    Handling Large Datasets in UI

    Coming soon
  • 43

    Testing Hooks and Components

    Coming soon
  • 44

    Managing Global Modals

    Coming soon
  • 45

    Implementing Keyboard Shortcuts

    Coming soon
  • 46

    Optimizing Asset Loading

    Coming soon
  • 47

    Internationalization Basics

    Coming soon
  • 48

    Managing WebSocket Connections

    Coming soon
  • View full course