← Back to Chapters

Multiple Input Fields

? Multiple Input Fields

⚡ Quick Overview

When dealing with forms containing many fields, creating a separate state variable for each input becomes repetitive and hard to scale. A better pattern in React is to keep all related fields inside a single state object or array and update them using dynamic keys.

With smart event handlers and the name attribute, React lets you manage fixed, dynamic, and even nested inputs using clean, reusable logic.

⚛️ React Forms • ? Dynamic Keys • ? Nested Objects

?️ Key Concepts

  • Use one state object for related fields like name, email, and phone.
  • Let a single handleChange function update fields using e.target.name.
  • Represent repeating inputs (skills, phone numbers, etc.) as arrays in state.
  • Use map() to render dynamic fields and handle them using their index.
  • Handle nested structures (like user.address) using the spread operator to avoid mutation.

? Syntax & Theory

For multiple inputs, we usually store values inside a single state object:

  • const [form, setForm] = useState({ name: "", email: "" });
  • Each input gets a name attribute matching the object key.
  • handleChange spreads the old state and overwrites only the changed field.

For dynamic lists (e.g., skills), we use arrays:

  • const [skills, setSkills] = useState([""]) to start with one empty skill.
  • Add items using setSkills([...skills, ""]).
  • Update or remove items using index-based operations and filter() or map().

For nested objects, we rely on nested spreads:

  • setUser({ ...user, address: { ...user.address, city: value } });
  • This keeps immutability and prevents accidental overwrites.

? Basic Example: Multiple Fields in One State

The simplest way to manage several inputs is with a single state object whose keys match each field’s name attribute.

? View Code Example
// React form with multiple fields managed in one state object
import React, { useState } from "react";

function MultiInputForm() {
const [form, setForm] = useState({
name: "",
email: "",
phone: ""
});

// Shared change handler that updates the correct field using its name
const handleChange = (e) => {
const { name, value } = e.target;
setForm({ ...form, [name]: value });
};

// Simple submit handler that shows the final form data as JSON
const handleSubmit = (e) => {
e.preventDefault();
alert(JSON.stringify(form, null, 2));
};

return (
<form onSubmit={handleSubmit}>
<h4>User Details</h4>

<div className="mb-3">
<label>Name</label>
<input
type="text"
name="name"
className="form-control"
value={form.name}
onChange={handleChange}
placeholder="Enter name"
/>
</div>

<div className="mb-3">
<label>Email</label>
<input
type="email"
name="email"
className="form-control"
value={form.email}
onChange={handleChange}
placeholder="Enter email"
/>
</div>

<div className="mb-3">
<label>Phone</label>
<input
type="tel"
name="phone"
className="form-control"
value={form.phone}
onChange={handleChange}
placeholder="Enter phone"
/>
</div>

<button className="btn btn-primary">Submit</button>
</form>
);
}

export default MultiInputForm;

All inputs share the same handleChange function. React uses the name of the input (name, email, phone) to update only that specific property inside form.

? Example: Dynamic Input Fields (Array-based)

For dynamic data (like multiple skills, phone numbers, or addresses), you can store values in an array. Every time you add or remove a field, you update the array and React re-renders the UI.

? View Code Example
// Dynamic skills form where users can add or remove skill inputs
import React from "react";

function DynamicFields() {
const [skills, setSkills] = React.useState([""]);

// Update a single skill based on its index in the skills array
const handleChange = (index, event) => {
const newSkills = [...skills];
newSkills[index] = event.target.value;
setSkills(newSkills);
};

// Add a new empty skill field at the end
const addField = () => setSkills([...skills, ""]);

// Remove a specific skill field using its index
const removeField = (index) => {
const updated = skills.filter((_, i) => i !== index);
setSkills(updated);
};

// Show all skills as a comma separated string when submitting
const handleSubmit = (e) => {
e.preventDefault();
alert("Skills: " + skills.join(", "));
};

return (
<form onSubmit={handleSubmit}>
<h4>Dynamic Skills Input</h4>

{skills.map((skill, index) => (
<div key={index} className="input-group mb-2">
<input
type="text"
className="form-control"
placeholder={`Skill ${index + 1}`}
value={skill}
onChange={(e) => handleChange(index, e)}
/>
<button
type="button"
className="btn btn-danger"
onClick={() => removeField(index)}
>
❌
</button>
</div>
))}

<button
type="button"
className="btn btn-outline-success me-2"
onClick={addField}
>
➕ Add Skill
</button>

<button className="btn btn-primary">Submit</button>
</form>
);
}

export default DynamicFields;

This pattern is common in job portals, survey builders, and custom form builders where the user can add or remove entries freely without changing your component’s structure.

⚙️ Example: Nested Objects (Address Form)

When your data model is nested (for example, a user with an address object inside), you can still keep everything in React state. Use nested spreads so you only update the part of the object you care about.

? View Code Example
// Form that manages a nested user object with an address inside
import React from "react";

function AddressForm() {
const [user, setUser] = React.useState({
name: "",
address: {
city: "",
pincode: ""
}
});

// Handle updates for top-level properties like user.name
const handleChange = (e) => {
const { name, value } = e.target;
setUser({ ...user, [name]: value });
};

// Safely update only nested address properties like city and pincode
const handleAddressChange = (e) => {
const { name, value } = e.target;
setUser({
...user,
address: { ...user.address, [name]: value }
});
};

return (
<div>
<h4>Nested Object Example</h4>
<input
type="text"
name="name"
className="form-control mb-2"
placeholder="Enter name"
value={user.name}
onChange={handleChange}
/>
<input
type="text"
name="city"
className="form-control mb-2"
placeholder="City"
value={user.address.city}
onChange={handleAddressChange}
/>
<input
type="text"
name="pincode"
className="form-control mb-2"
placeholder="Pincode"
value={user.address.pincode}
onChange={handleAddressChange}
/>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
);
}

export default AddressForm;

The nested spread pattern lets you update user.address.city or user.address.pincode without losing other properties on user or user.address.

? Live Output & Explanation

  • MultiInputForm: As you type into the name, email, and phone fields, the form state updates instantly. On submit, a JSON popup shows all entered data.
  • DynamicFields: Clicking ➕ Add Skill adds a new input. Each ❌ button removes the corresponding skill. Submit displays the collected skills as a comma-separated list.
  • AddressForm: Typing into the inputs updates the nested user object. The JSON.stringify preview shows a live JSON representation of the full user data.

Together, these patterns cover most real-world form requirements: fixed fields, dynamic lists, and nested data.

? Best Practices

  • ✅ Use a single handleChange for all inputs where possible.
  • ✅ Use the name attribute as the key for updating state dynamically.
  • ✅ For array-based inputs, use map() with stable key props.
  • ✅ Always use the spread operator (...) instead of mutating state directly.
  • ✅ Keep state shapes close to your data model (e.g., nested objects for nested entities).

? Tips

  • Always initialize all form fields in your state to avoid uncontrolled component warnings.
  • Extract complex logic into helper functions or custom hooks for cleaner components.
  • Use useReducer() when forms become large, deeply nested, or heavily validated.
  • When adding/removing fields dynamically, ensure each item has a unique and stable key.

? Try It Yourself

  1. Create a dynamic contact form that allows adding/removing multiple phone numbers for a single user.
  2. Add validation to prevent empty fields before submission and show friendly error messages.
  3. Display form data in a live JSON preview panel beside the form.
  4. Refactor your form logic into a reusable custom hook (for example, useForm) that manages values, change handlers, and reset logic.

Goal: Learn to efficiently handle multiple and dynamic form inputs using arrays, nested objects, and reusable event handlers for scalable React form design.