Hello! Welcome to the final lesson of our module on state management and forms in React.
In previous lessons, you've learned to manage form state with useState, create controlled inputs, handle validation, and even share state between components by "lifting it up." You've built a solid foundation for handling user input.
Today, we'll put all of that together. Our goal is to build a multi-field contact form that consolidates and logs form data on submission. This is a capstone project for this module and a very common task in web development. We'll focus on the most efficient and scalable way to manage state for forms with multiple fields.
1. The Challenge: Managing State for Multiple Inputs
So far, we've mostly handled one or two inputs at a time. If you have a form with many fields (name, email, address, message, etc.), creating a separate useState call for each one can get messy quickly.
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [address, setAddress] = useState('');
// ...and so on
This approach works, but it leads to cluttered code and multiple, repetitive onChange handler functions. As a developer, you know that repetitive code is a sign that there's often a more elegant, maintainable solution.
2. A Better Way: Consolidating State into an Object
A much cleaner pattern is to manage all the form's data within a single state object.
Instead of many state variables, you'll have one:
const [formDetails, setFormDetails] = useState({
name: '',
email: '',
address: ''
});
This approach keeps all related data together. In your work with Laravel, you can think of this state object as being analogous to a Request object or a Model that holds all the data from a form submission in one place before you process it.
The video below demonstrates this exact pattern. It's a complete walkthrough of building a multi-field form, from setting up the JSX to handling the submission.
Handling Complex Multiple Input Form States in React [2 LINES ONLY]
This video from Code Bless You provides a concise guide to handling a multi-field form using a single state object. We will follow its structure to build our contact form.
First, watch from 00:30 to 02:16. This part covers: Setting up the basic JSX for a form with several input fields. Initializing a single state variable using useState with an object containing properties for each input.
As you can see, this immediately makes the state management for the component cleaner and more organized.
3. The Generic handleChange Function
Now that the state is in a single object, how do you update it? Do you need a separate function to update the name, another for the email, and so on? No! You can create a single, generic handleChange function that works for all inputs.
This is where controlled components and a little bit of ES6 syntax come together beautifully. The key is to ensure that each input element has a name attribute that exactly matches the corresponding key in your state object.

The handleChange function uses the event object (e) to figure out which input changed and what its new value is.
Handling Complex Multiple Input Form States in React [2 LINES ONLY]
Now, let's look at the core logic. This part of the video shows how to create one handler function to rule them all.
Continue watching from 02:16 to 05:38. Pay close attention to these key steps: Assigning name attributes: Notice how the name attribute of each <input> matches a key in the details state object (<input name="email" ...>). The handleChange logic: The magic happens here. The function uses e.target.name to identify which field to update and e.target.value for the new value. The Spread Operator and Computed Property Names: The state setter uses the functional update form and this crucial line: return { ...prev, [name]: value };. This creates a new object by copying the previous state (...prev) and then overwriting just the one property that changed. The [name] syntax is an ES6 feature called computed property names, which allows you to use a variable as a property key in an object literal.
This pattern is incredibly powerful and is the standard way to handle forms in React. For a quick text-based summary of this exact technique, you can also refer to the article "How to handle multiple inputs in React" from DEV Community.
4. Handling the Form Submission
The final step is to handle what happens when the user clicks the "Submit" button. You need an onSubmit event listener on your <form> tag. The handler function it calls should do two things:
- Prevent the default browser behavior. By default, submitting a form reloads the page. You must call
e.preventDefault()to stop this. - Access the data. Because all your form's data is neatly contained in your state object, you can now easily access it and, for now, log it to the console. In a real application, this is where you would send the data to an API.
Handling Complex Multiple Input Form States in React [2 LINES ONLY]
Let's finish the form by implementing the submission logic.
Watch the final part of the video from 05:38 to 06:41. It shows how to create an handleSubmit function, call e.preventDefault(), and log the final, consolidated details object to the console.
5. Your Turn: Build the Contact Form
Now it's time to build it. Create a new component named ContactForm.jsx. Use the concepts from the video and the code below as a guide to build a complete, working multi-field form.
Here is the complete code for the component, bringing everything together. Review it and make sure you understand how each piece connects.
import { useState } from 'react';
function ContactForm() {
// 1. A single state object to hold all form data
const [formDetails, setFormDetails] = useState({
name: '',
email: '',
message: ''
});
// 2. A generic handler to update the state object
const handleChange = (e) => {
const { name, value } = e.target;
setFormDetails(prevDetails => {
return {
...prevDetails,
[name]: value
};
});
};
// 3. A submit handler to process the data
const handleSubmit = (e) => {
// Prevent the default page refresh
e.preventDefault();
// Log the consolidated data
console.log('Form Submitted:', formDetails);
// Optional: clear the form after submission
setFormDetails({
name: '',
email: '',
message: ''
});
};
return (
<div style={{ maxWidth: '400px', margin: 'auto', padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h3>Contact Us</h3>
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '15px' }}>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
name="name" // Matches the state key
value={formDetails.name}
onChange={handleChange}
required
style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }}
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email" // Matches the state key
value={formDetails.email}
onChange={handleChange}
required
style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }}
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label htmlFor="message">Message</label>
<textarea
id="message"
name="message" // Matches the state key
value={formDetails.message}
onChange={handleChange}
required
rows="4"
style={{ width: '100%', padding: '8px', boxSizing: 'border-box' }}
></textarea>
</div>
<button type="submit" style={{ width: '100%', padding: '10px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px' }}>
Submit
</button>
</form>
</div>
);
}
export default ContactForm;
Try running this component in a React project. Fill out the form and check the console when you submit. You'll see the complete data object logged, just as planned.
Conclusion
Congratulations on completing this module! You have successfully built a clean, scalable, and maintainable multi-field form in React.
Key Takeaways:
- For forms with multiple fields, it's best to consolidate state into a single object with
useState. - A single, generic
handleChangefunction can update any field in the state object. - This requires each input to have a
nameattribute that matches a key in the state object. - The logic
[e.target.name]: e.target.valueuses computed property names to dynamically update the correct state property. - Always use
e.preventDefault()in youronSubmithandler to prevent the browser from reloading the page.
Preview of the Next Module:
You've now mastered collecting data from users. The next logical step is to do something with that data! In the next module, "Handling Side Effects and API Integration," we'll introduce the useEffect hook. You will learn how to fetch data from a server when your component loads and, importantly, how to send your form data to a backend API. This will be a huge step toward building full-stack applications.