Forms are essential for user interaction in React applications. Here’s a complete guide to handling forms effectively:
1. Controlled Components (Recommended Approach)
import { useState } from 'react';
function BasicForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted:', formData);
// Submit to API here
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
placeholder="Username"
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="Password"
/>
<button type="submit">Submit</button>
</form>
);
}
2. Form Validation
Basic Validation
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!formData.username.trim()) {
newErrors.username = 'Username is required';
}
if (!formData.email.includes('@')) {
newErrors.email = 'Valid email is required';
}
if (formData.password.length < 6) {
newErrors.password = 'Password must be at least 6 characters';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
// Submit form
}
};
Real-time Validation
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
// Validate on change
if (name === 'email' && !value.includes('@')) {
setErrors(prev => ({ ...prev, email: 'Invalid email' }));
} else {
setErrors(prev => ({ ...prev, email: '' }));
}
};
3. Form Libraries
React Hook Form (Recommended)
import { useForm } from 'react-hook-form';
function HookForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("username", { required: true })}
placeholder="Username"
/>
{errors.username && <span>This field is required</span>}
<input
{...register("email", {
required: "Email is required",
pattern: {
value: /^\S+@\S+$/i,
message: "Invalid email address"
}
})}
placeholder="Email"
/>
{errors.email && <span>{errors.email.message}</span>}
<button type="submit">Submit</button>
</form>
);
}
Formik with Yup Validation
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const validationSchema = Yup.object().shape({
username: Yup.string().required('Required'),
email: Yup.string().email('Invalid email').required('Required'),
password: Yup.string().min(6, 'Too short').required('Required')
});
function FormikForm() {
return (
<Formik
initialValues={{ username: '', email: '', password: '' }}
validationSchema={validationSchema}
onSubmit={(values) => console.log(values)}
>
<Form>
<Field name="username" placeholder="Username" />
<ErrorMessage name="username" component="div" />
<Field name="email" type="email" placeholder="Email" />
<ErrorMessage name="email" component="div" />
<Field name="password" type="password" placeholder="Password" />
<ErrorMessage name="password" component="div" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
}
4. Advanced Form Techniques
Dynamic Fields
function DynamicForm() {
const [fields, setFields] = useState([{ id: 1, value: '' }]);
const addField = () => {
setFields([...fields, { id: Date.now(), value: '' }]);
};
const removeField = (id) => {
setFields(fields.filter(field => field.id !== id));
};
const handleChange = (id, value) => {
setFields(fields.map(field =>
field.id === id ? { ...field, value } : field
));
};
return (
<form>
{fields.map(field => (
<div key={field.id}>
<input
value={field.value}
onChange={(e) => handleChange(field.id, e.target.value)}
/>
<button type="button" onClick={() => removeField(field.id)}>
Remove
</button>
</div>
))}
<button type="button" onClick={addField}>
Add Field
</button>
</form>
);
}
File Uploads
function FileUpload() {
const [file, setFile] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
// Handle response
} catch (error) {
console.error('Upload failed:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="file"
onChange={(e) => setFile(e.target.files[0])}
/>
<button type="submit">Upload</button>
</form>
);
}
5. Performance Optimization
Debounced Inputs
import { useDebounce } from 'use-debounce';
function SearchForm() {
const [query, setQuery] = useState('');
const [debouncedQuery] = useDebounce(query, 500);
useEffect(() => {
if (debouncedQuery) {
// Perform search with debouncedQuery
}
}, [debouncedQuery]);
return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}
Optimized Large Forms
// Split form into smaller components with React.memo
const PersonalInfoSection = React.memo(({ values, onChange }) => (
<section>
<input
name="firstName"
value={values.firstName}
onChange={onChange}
/>
{/* More fields... */}
</section>
));
// In parent form component
const handleSectionChange = useCallback((name, value) => {
setFormData(prev => ({ ...prev, [name]: value }));
}, []);
Choosing the Right Approach
- Simple forms: Controlled components with useState
- Complex forms with validation: React Hook Form or Formik
- Forms with complex relationships: Consider state management (Redux, Zustand)
- High-performance needs: Optimize with memoization and debouncing
Remember to:
- Always validate on both client and server sides
- Provide clear feedback for errors
- Consider accessibility (labels, ARIA attributes)
- Test form behavior thoroughly