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
name, email, and phone.handleChange function update fields using e.target.name.map() to render dynamic fields and handle them using their index.user.address) using the spread operator to avoid mutation.For multiple inputs, we usually store values inside a single state object:
const [form, setForm] = useState({ name: "", email: "" });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.setSkills([...skills, ""]).filter() or map().For nested objects, we rely on nested spreads:
setUser({ ...user, address: { ...user.address, city: value } });The simplest way to manage several inputs is with a single state object whose keys match each field’s name attribute.
// 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.
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.
// 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.
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.
// 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.
name, email, and phone fields, the form state updates instantly. On submit, a JSON popup shows all entered data.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.
handleChange for all inputs where possible.name attribute as the key for updating state dynamically.map() with stable key props....) instead of mutating state directly.useReducer() when forms become large, deeply nested, or heavily validated.key.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.