Form handling in React differs fundamentally from traditional HTML/JavaScript. In React, input data is typically handled by the components, not the DOM. This leads to the concept of Controlled Components.
A Controlled Component is an input element (like <input>, <textarea>, or <select>) whose value is entirely controlled by a piece of component state. Every state change is handled by a function, making the data flow explicit and predictable.
1. The Controlled Component Workflow
The standard workflow for controlling an input involves three steps:
- State Initialization: Use the
useStatehook to set the initial value of the input. - Value Binding: Bind the input’s
valueattribute to the state variable. - Change Handling: Attach an
onChangeevent handler that updates the state every time the input value changes.
2. Execution Example: Single Input Form
Let’s create a login form where the input field is controlled by state.
In your my-first-app/src directory, open App.js and replace its content with the following code:
import React, { useState } from 'react';
function LoginForm() {
// 1. Initialize State for the input value and the submission message
const [username, setUsername] = useState('');
const [message, setMessage] = useState('');
// 2. Change Handler: Updates the state on every keystroke
const handleUsernameChange = (event) => {
// The event.target.value is the current content of the input field
setUsername(event.target.value);
};
// 3. Submission Handler
const handleSubmit = (event) => {
// Prevent the default browser form submission (which causes a page reload)
event.preventDefault();
// Display the submitted state value
setMessage(`Form submitted! User: ${username}`);
// Optional: Clear the form field after submission
setUsername('');
};
return (
<div className="FormDemo">
<h1>Controlled Input Form</h1>
{/* Attach handleSubmit to the form's onSubmit event */}
<form onSubmit={handleSubmit}>
<label>
Username:
{/* 4. Value Binding: The input value is tied directly to the state */}
{/* 5. Change Handling: The onChange event calls the handler */}
<input
type="text"
value={username}
onChange={handleUsernameChange}
/>
</label>
<button type="submit">Log In</button>
</form>
{/* Display current state and submission message */}
<p>Current Input Value: {username}</p>
<p style={{ color: 'blue' }}>{message}</p>
</div>
);
}
export default LoginForm;
- Local Execution: Run
npm startin your terminal. - Observation: As you type, the “Current Input Value” updates immediately, demonstrating that the state is constantly synchronized with the input field.
3. Handling Multiple Inputs
When managing a form with many fields (e.g., name, email, password), it’s best practice to consolidate all related fields into a single state object rather than creating separate useState calls for each field.
Execution Example: State Object for Multiple Inputs
We use the event target’s name attribute to dynamically determine which key in the state object to update.
import React, { useState } from 'react';
function MultiInputForm() {
// 1. Initialize state as an object
const [formData, setFormData] = useState({
firstName: '',
email: ''
});
// 2. Universal Change Handler
const handleChange = (event) => {
const { name, value } = event.target;
// Use the spread operator to copy the existing state,
// and then overwrite only the single field that changed (using [name] as the key).
setFormData(prevData => ({
...prevData,
[name]: value
}));
};
const handleSubmit = (event) => {
event.preventDefault();
console.log("Submitted Data:", formData);
};
return (
<form onSubmit={handleSubmit}>
<label>
First Name:
<input
type="text"
name="firstName" // Must match the state key
value={formData.firstName}
onChange={handleChange}
/>
</label>
<label>
Email:
<input
type="email"
name="email" // Must match the state key
value={formData.email}
onChange={handleChange}
/>
</label>
<button type="submit">Submit</button>
</form>
);
}
export default MultiInputForm;
This pattern scales efficiently for complex forms, requiring only one change handler function.
