Multi-Step Forms
In this page:
const [step, setStep] = useState(0);
const [formData, setFormData] = useState({});
{step === 0 && <StepOne />}
{step === 1 && <StepTwo />}
<button onClick={() => setStep(s => s - 1)}>Back</button>
<button onClick={() => setStep(s => s + 1)}>Next</button>
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.
Example: Tracking the Current Step
<!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.
Example: Sharing Data Across Steps
<!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.
Example: Adding Back Navigation and Step Boundaries
<!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>
- Losing the previous step's data when moving to the next step because it wasn't lifted into shared state.
- Not validating the current step before allowing Next, letting users skip required fields.
- Resetting the step counter accidentally on every re-render instead of only on button clicks.
- 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.
No React-version restriction — built entirely from useState and conditional rendering.
Chapter Quiz — Complete all 8 topics to unlock
0/8 topics done
Complete these topics first: