SevyDevy

Guide · Frontend

Frontend Machine Coding: How to Build a Multi-Step Form

16 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

A detailed guide to building a multi-step form for frontend machine coding rounds, covering state management, validation, navigation, and real-world UX patterns.

Why Multi-Step Form Is Asked in Interviews

Multi-step forms are frequently asked in frontend machine coding rounds because they test structured thinking, state management, validation handling, and user flow design. Unlike simple forms, this problem requires managing data across multiple screens while maintaining a seamless experience.

It also reflects real-world use cases such as onboarding flows, checkout processes, loan applications, and profile creation systems.

What Problem Are We Solving?

Instead of showing a long and overwhelming form, multi-step forms break the input process into smaller steps. Each step collects a subset of data, improving user experience and completion rates.

Visualizing the User Experience

Imagine a signup flow where the user first enters personal details, then address information, and finally account credentials. The user clicks 'Next' to move forward and 'Back' to revisit previous steps. Progress is preserved across steps, and validation ensures correctness before proceeding.

Core Requirements

  • Multiple steps with navigation (Next / Back)
  • Shared form state across steps
  • Validation per step
  • Prevent navigation on invalid input
  • Final submission with all data
  • Optional progress indicator

Step 1: State Design

The key to solving this problem is managing a centralized state that persists across steps. Instead of separate states per step, a single object should hold all form data.

tsx
const [step, setStep] = useState(1);

const [formData, setFormData] = useState({
  name: "",
  email: "",
  address: "",
  city: "",
  password: "",
});

This ensures that moving between steps does not reset previously entered data.

Step 2: Handling Input Changes

A generic handler simplifies updates across all fields.

tsx
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { name, value } = e.target;

  setFormData((prev) => ({
    ...prev,
    [name]: value,
  }));
};

Step 3: Navigation Logic

Navigation between steps should be controlled and predictable.

tsx
const nextStep = () => setStep((prev) => prev + 1);
const prevStep = () => setStep((prev) => prev - 1);

Step 4: Step-wise Validation

Each step should validate only its relevant fields before allowing the user to proceed.

tsx
const validateStep = () => {
  if (step === 1) {
    return formData.name && formData.email;
  }

  if (step === 2) {
    return formData.address && formData.city;
  }

  if (step === 3) {
    return formData.password.length >= 6;
  }

  return true;
};

Step 5: Rendering Steps

Each step renders only its relevant inputs.

tsx
const renderStep = () => {
  switch (step) {
    case 1:
      return (
        <>
          <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
          <input name="email" value={formData.email} onChange={handleChange} placeholder="Email" />
        </>
      );

    case 2:
      return (
        <>
          <input name="address" value={formData.address} onChange={handleChange} placeholder="Address" />
          <input name="city" value={formData.city} onChange={handleChange} placeholder="City" />
        </>
      );

    case 3:
      return (
        <>
          <input name="password" type="password" value={formData.password} onChange={handleChange} placeholder="Password" />
        </>
      );

    default:
      return null;
  }
};

Step 6: Complete Component

tsx
import React, { useState } from "react";

export default function MultiStepForm() {
  const [step, setStep] = useState(1);

  const [formData, setFormData] = useState({
    name: "",
    email: "",
    address: "",
    city: "",
    password: "",
  });

  const handleChange = (e: any) => {
    const { name, value } = e.target;

    setFormData((prev) => ({
      ...prev,
      [name]: value,
    }));
  };

  const validateStep = () => {
    if (step === 1) return formData.name && formData.email;
    if (step === 2) return formData.address && formData.city;
    if (step === 3) return formData.password.length >= 6;
    return true;
  };

  const nextStep = () => {
    if (!validateStep()) {
      alert("Please fill all fields correctly");
      return;
    }
    setStep((prev) => prev + 1);
  };

  const prevStep = () => setStep((prev) => prev - 1);

  const handleSubmit = () => {
    if (!validateStep()) return;
    console.log("Final Data:", formData);
  };

  const renderStep = () => {
    switch (step) {
      case 1:
        return (
          <>
            <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
            <input name="email" value={formData.email} onChange={handleChange} placeholder="Email" />
          </>
        );
      case 2:
        return (
          <>
            <input name="address" value={formData.address} onChange={handleChange} placeholder="Address" />
            <input name="city" value={formData.city} onChange={handleChange} placeholder="City" />
          </>
        );
      case 3:
        return (
          <>
            <input name="password" type="password" value={formData.password} onChange={handleChange} placeholder="Password" />
          </>
        );
      default:
        return null;
    }
  };

  return (
    <div>
      <h2>Step {step}</h2>

      {renderStep()}

      <div>
        {step > 1 && <button onClick={prevStep}>Back</button>}
        {step < 3 && <button onClick={nextStep}>Next</button>}
        {step === 3 && <button onClick={handleSubmit}>Submit</button>}
      </div>
    </div>
  );
}

How the Flow Works

The user fills Step 1 and clicks Next. If validation passes, Step 2 appears. Data persists across steps. The user can go back and edit previous steps. Finally, all data is submitted together.

Common Mistakes

  • Losing data when switching steps
  • Not validating before moving forward
  • Hardcoding step logic instead of scalable design
  • Coupling UI and logic too tightly

Advanced Improvements

  • Add progress bar
  • Persist data in localStorage
  • Use React Hook Form + Zod
  • Add async validation (email check)
  • Add stepper UI (like checkout)

Real-World Example

In an e-commerce checkout, Step 1 collects shipping info, Step 2 collects payment details, and Step 3 confirms the order. This structure improves user completion rate compared to a long single form.

Final Takeaway

Multi-step forms test how well you manage state, validation, and user flow. A strong implementation keeps logic clean, prevents invalid transitions, and ensures smooth navigation.

In machine coding rounds, the goal is not just to make it work but to design it like a production-ready component.

Practise this with Lala

Practise