Building a Tab Form Component in React

Here is a detailed breakdown and explanation of how you built the multi-step tab-based form component in React, step-by-step, along with code snippets and explanations of logic, validation, state management, and navigation.

✅ Objective

You created a multi-step Tab Form Component where:

  • The user fills in profile details, selects interests, and chooses theme settings.
  • Navigation between tabs is allowed only if the current tab is valid.
  • The form is broken into 3 tabs: Profile, Interests, and Settings.

✅ 1. Overall State and Structure (Tabs.js)

🧠 Core States

const [activeTabIndex, setActiveTabIndex] = useState(0); // Track current tab
              const [data, setData] = useState({
                name: "Yogesh Arya",
                age: "20",
                email: "yogesh@gmil.com",
                interests: ["Drawing"],
                theme: "dark",
              });
              const [errors, setErrors] = useState({});
  • activeTabIndex: Controls which tab is currently active.
  • data: Holds the form data shared across all tabs.
  • errors: Holds validation error messages for each field.

✅ 2. Tab Configuration with Validation Logic

const tabs = [
              {
                name: "Profile",
                component: Profile,
                validate: () => {
                  const err = {};
                  if (!data.name || data.name.length < 2) err.name = "Invalid Name";
                  if (!data.age || data.age < 18) err.age = "Invalid Age";
                  if (!data.email || data.email.length < 2) err.email = "Invalid Email";
                  setErrors(err);
                  return !(err.name || err.age || err.email);
                },
              },
              {
                name: "Interests",
                component: Interests,
                validate: () => {
                  const err = {};
                  if (data.interests.length < 1) err.interests = "Invalid Interests";
                  setErrors(err);
                  return !err.interests;
                },
              },
              {
                name: "Settings",
                component: Settings,
                validate: () => true, // No validation
              },
            ];

Each tab:

  • Has a name, a component, and a validate function.
  • validate() is run before moving to the next tab or switching manually.

✅ 3. Dynamic Component Rendering

const TabBody = tabs[activeTabIndex]?.component;
<TabBody data={data} setData={setData} errors={errors} />
  • The active component (Profile, Interests, or Settings) is rendered based on activeTabIndex.

✅ 4. Navigation Logic

const handlePrev = () => {
  setActiveTabIndex((prev) => prev - 1);
};

const handleNext = () => {
  if (tabs[activeTabIndex].validate()) {
    setActiveTabIndex((prev) => prev + 1);
  }
};

const handleSubmit = () => {
  console.log("Submitting:", data);
};
  • Next: Proceeds only if current tab is validation passes.
  • Prev: Goes back without validation.
  • Submit: Shown only on the last tab.

✅ 5. Tab Header Buttons

<div className="tabs">
  {tabs.map((tab, index) => (
    <button
      key={index}
      onClick={() =>
        tabs[activeTabIndex].validate() && setActiveTabIndex(index)
      }
    >
      {tab.name}
    </button>
  ))}
</div>
  • Clicking a tab header will only change tabs if the current tab is valid.
  • Prevents skipping validation by jumping directly to another tab.

✅ 6. Profile Tab (Profile.js)

Purpose: Enter name, age, and email.

<input
  type="text"
  name="name"
  value={data.name}
  onChange={handleChange}
/>
{errors.name && <p className="error">{errors.name}</p>}

Logic:

  • Controlled inputs using data and setData.
  • Shows error messages based on errors.

✅ 7. Interests Tab (Interests.js)

Purpose: Choose one or more interests via checkboxes.

<input
  type="checkbox"
  checked={data.interests.includes("Coding")}
  onChange={(e) => handleChangeInterests(e, "Coding")}
/>

Logic:

  • Toggles interest selection.
  • Validation ensures at least one is selected.
setData((prev) => ({
  ...prev,
  interests: e.target.checked
    ? [...prev.interests, value]
    : prev.interests.filter((val) => val !== value),
}));
            

✅ 8. Settings Tab (Settings.js)

Purpose: Select theme (radio button for dark or light).

<input
  type="radio"
  checked={data.theme === "dark"}
  name="dark"
  onChange={handleThemeChange}
/>

Logic:

  • Controlled via data.theme
  • Updates theme in global form data.

✅ 9. Final Buttons

{activeTabIndex > 0 && <button onClick={handlePrev}>prev</button>}
{activeTabIndex < tabs.length - 1 && (
  <button onClick={handleNext}>next</button>
)}
{activeTabIndex === tabs.length - 1 && (
  <button onClick={handleSubmit}>Submit</button>
)}
  • Conditionally renders Prev/Next/Submit buttons based on the tab.

✅ 10. Example Form Flow

  1. Profile → Fill name, age (≥18), and email → Click Next
  2. Interests → Select at least one checkbox → Click Next
  3. Settings → Choose a theme → Click Submit

✅ Enhancement Ideas

  • Add animations between tab transitions.
  • Disable tabs that are not yet valid.
  • Show step indicators (Step 1 of 3).
  • Highlight tab headers with errors.