Handling Forms in React: From Controlled Components to React Hook Form & Validation
Forms remain one of the most complex aspects of frontend engineering. This guide demystifies the pattern of Controlled Components, details strategies for handling multi-input state and file uploads, and provides a production-grade comparison of validation ecosystems—focusing on modern solutions like React Hook Form and Formik.
Forms in React
Controlled Forms
Controlled forms are React's recommended pattern where form element values are controlled by React state, making React the "single source of truth." Every keystroke triggers a state update, giving you full control over the input data.
const [value, setValue] = useState(''); <input value={value} onChange={(e) => setValue(e.target.value)} />
Form State Management
Form state management involves tracking all form values, validation errors, touched fields, and submission status in a structured way, typically using an object state or specialized hooks to handle complex forms efficiently.
const [form, setForm] = useState({ name: '', email: '', errors: {} });
Multiple Inputs
Handle multiple inputs efficiently by using a single handler function with computed property names, using the input's name attribute to identify which field to update in your state object.
const handleChange = (e) => { const { name, value } = e.target; setForm(prev => ({ ...prev, [name]: value })); }; <input name="email" value={form.email} onChange={handleChange} />
Select Elements
Select elements in React work similarly to text inputs but use the value prop on the <select> tag rather than a selected attribute on options, making it consistent with other controlled inputs.
const [fruit, setFruit] = useState('apple'); <select value={fruit} onChange={(e) => setFruit(e.target.value)}> <option value="apple">Apple</option> <option value="banana">Banana</option> </select>
Textarea Elements
Unlike HTML where textarea content goes between tags, React's textarea uses a value prop just like text inputs, providing a consistent API across all form elements.
<textarea value={text} onChange={(e) => setText(e.target.value)} />
Checkbox Inputs
Checkboxes use the checked prop (boolean) instead of value, and you toggle state based on e.target.checked rather than e.target.value.
const [agreed, setAgreed] = useState(false); <input type="checkbox" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} />
Radio Buttons
Radio buttons share the same name attribute and use checked prop to determine selection; the value indicates which option is selected when comparing against the current state.
const [color, setColor] = useState('red'); <input type="radio" name="color" value="red" checked={color === 'red'} onChange={(e) => setColor(e.target.value)} /> <input type="radio" name="color" value="blue" checked={color === 'blue'} onChange={(e) => setColor(e.target.value)} />
File Inputs
File inputs are inherently uncontrolled because their value is read-only for security reasons; you access files through e.target.files and typically store the File object in state.
const [file, setFile] = useState(null); <input type="file" onChange={(e) => setFile(e.target.files[0])} />
Form Validation
Form validation can be done on change, blur, or submit; it involves checking field values against rules and storing error messages in state to display feedback to users.
const validate = (values) => { const errors = {}; if (!values.email) errors.email = 'Required'; else if (!/\S+@\S+\.\S+/.test(values.email)) errors.email = 'Invalid email'; return errors; };
Form Libraries (Formik, React Hook Form)
Form libraries abstract away boilerplate for validation, error handling, and submission; React Hook Form uses refs for better performance, while Formik uses controlled components with a more declarative API.
// React Hook Form const { register, handleSubmit, formState: { errors } } = useForm(); <input {...register('email', { required: true })} /> // Formik <Formik initialValues={{ email: '' }} onSubmit={handleSubmit}> <Field name="email" /> </Formik>
Uncontrolled Forms with Refs
Uncontrolled forms let the DOM handle form state; you access values via refs when needed (typically on submit), which can be simpler for basic forms or integrating with non-React code.
const inputRef = useRef(); const handleSubmit = () => console.log(inputRef.current.value); <input ref={inputRef} defaultValue="initial" />