Forms in React
Handling user input via forms is a crucial part of most web applications. React provides a robust way to manage form state, primarily through a pattern known as "Controlled Components."
Controlled Components
In plain HTML, form elements like <input>, <textarea>, and <select> typically maintain their own state based on user input.
In React, we usually pull that state out of the DOM and manage it entirely within the component's state using useState. An input whose value is controlled by React is called a Controlled Component.
Text Inputs
To create a controlled input, you must provide two things:
- A
valueprop tied to a state variable. - An
onChangehandler that updates that state variable.
import React, { useState } from 'react';
export default function SimpleForm() {
const [name, setName] = useState('');
const handleChange = (e) => {
// e.target.value contains the current text in the input
setName(e.target.value);
};
return (
<div>
<input type="text" value={name} onChange={handleChange} />
<p>Hello, {name}!</p>
</div>
);
}
Textareas
In HTML, the value of a <textarea> is usually defined by its children (e.g., <textarea>Hello</textarea>). In React, a <textarea> acts exactly like an <input type="text"> using the value prop.
const [bio, setBio] = useState('Default bio text');
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
/>
Select Dropdowns
Similarly, HTML uses the selected attribute on <option> tags. React, however, uses a value prop on the root <select> tag.
const [flavor, setFlavor] = useState('chocolate');
<select value={flavor} onChange={(e) => setFlavor(e.target.value)}>
<option value="vanilla">Vanilla</option>
<option value="chocolate">Chocolate</option>
<option value="strawberry">Strawberry</option>
</select>
Form Submission
To handle the submission of a form, you attach an onSubmit event handler to the <form> element itself, not to the submit button.
Crucial Step: You must call e.preventDefault() inside your submit handler. Otherwise, the browser will execute its default behavior, which is to reload the page!
import React, { useState } from 'react';
export default function RegistrationForm() {
const [formData, setFormData] = useState({
username: '',
email: ''
});
const handleInputChange = (e) => {
const { name, value } = e.target;
// Update object state dynamically
setFormData((prevData) => ({
...prevData,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault(); // Prevent page reload
console.log('Submitting data:', formData);
// Here you would typically send the data to an API
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username: </label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleInputChange}
/>
</div>
<div>
<label>Email: </label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
/>
</div>
<button type="submit">Register</button>
</form>
);
}
Uncontrolled Components (Alternative)
Sometimes, you don't need React to track every single keystroke. If you just need the value of an input at the exact moment a form is submitted, you can use Uncontrolled Components via the useRef hook.
import React, { useRef } from 'react';
export default function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
alert(`A name was submitted: ${inputRef.current.value}`);
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
{/* No 'value' or 'onChange', just a 'ref' */}
<input type="text" ref={inputRef} />
</label>
<button type="submit">Submit</button>
</form>
);
}
Rule of Thumb: Default to Controlled Components. Use Uncontrolled Components (refs) only when integrating with non-React libraries or when performance issues arise from too many state updates on large, complex forms.