← Back to React Course | Chapter 7: Forms & User Input | Lesson 5 of 8

Multi-Step Forms

A multi-step form is like a staircase — you climb one step of questions at a time instead of seeing the whole flight at once.

Tracking the Current Step

A multi-step form uses one piece of state, usually a number, to track which step is currently visible. Conditional rendering based on this step number decides which set of fields to display, while all steps share the same surrounding form.

Note: Store the step as a number (0, 1, 2...) rather than a string name, so Next/Back can simply add or subtract 1.

Warning: Don't reset the step to 0 inside the component body — that would run on every render, not just on button click.

Example: Tracking the Current Step

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function App() {
  const [step, setStep] = React.useState(0);
  return (
    <div>
      {step === 0 && <p>Step 1: Enter your name</p>}
      {step === 1 && <p>Step 2: Enter your email</p>}
      <button onClick={() => setStep(s => s + 1)}>Next</button>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Sharing Data Across Steps

Since each step shows different fields, the form's data needs to live in one shared state object above the step logic, not reset per step. Each step's inputs read from and write to this same shared object, so nothing is lost when moving forward or back.

Note: Use the same {...form, [field]: value} update pattern from single-page forms — multi-step forms are just one big form with staged visibility.

Warning: Creating a fresh state object per step (instead of one shared object) throws away data entered in earlier steps.

Example: Sharing Data Across Steps

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function App() {
  const [step, setStep] = React.useState(0);
  const [form, setForm] = React.useState({ name: "", email: "" });
  return (
    <div>
      {step === 0 && <input value={form.name} onChange={e => setForm({...form, name: e.target.value})} placeholder="Name" />}
      {step === 1 && <input value={form.email} onChange={e => setForm({...form, email: e.target.value})} placeholder="Email" />}
      <button onClick={() => setStep(s => s + 1)}>Next</button>
      <p>Saved: {form.name} / {form.email}</p>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>

Adding Back Navigation and Step Boundaries

A Back button decrements the step, but needs a boundary check so it can't go below the first step. Similarly, the Next button on the last step usually becomes a Submit button instead.

Note: Use Math.max(0, step - 1) for Back so it never goes negative, and Math.min for Next if you want a hard upper bound too.

Warning: Without clamping, clicking Back repeatedly on step 0 can push the step index into negative numbers, showing no matching content.

Example: Adding Back Navigation and Step Boundaries

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
function App() {
  const [step, setStep] = React.useState(0);
  const isLast = step === 1;
  return (
    <div>
      <p>Current step: {step + 1} of 2</p>
      <button onClick={() => setStep(s => Math.max(0, s - 1))} disabled={step === 0}>Back</button>
      <button onClick={() => isLast ? alert("Submitted!") : setStep(s => s + 1)}>{isLast ? "Submit" : "Next"}</button>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
  </script>
</body>
</html>
Common Mistakes
  1. Losing the previous step's data when moving to the next step because it wasn't lifted into shared state.
  2. Not validating the current step before allowing Next, letting users skip required fields.
  3. Resetting the step counter accidentally on every re-render instead of only on button clicks.
Chapter Summary
  • A multi-step form splits a long form into separate pages controlled by a step index in state.
  • All field data is usually stored in one shared state object across every step.
  • Next/Back buttons increment or decrement the step index rather than navigating pages.
  • Validating each step before allowing progression keeps bad data from reaching later steps.
Browser Support

No React-version restriction — built entirely from useState and conditional rendering.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.