Skip to content

React Forms

Forms are essential in web applications for collecting user input, handling interactions, and submitting data. In React, form management can be handled through controlled components, uncontrolled components, and form validation techniques.

Prerequisites

Before diving in, you should have:

  • Basic understanding of React and its hooks
  • Familiarity with HTML forms
  • Node.js and npm installed

For form validation libraries, you’ll need to install:

Terminal window
# For Formik
npm install formik
# For React Hook Form
npm install react-hook-form
# For Yup validation
npm install yup

Controlled Components

Controlled components are form elements where React controls their state through component state and event handlers. The input’s value is always derived from the React state, making it predictable and easier to manage.

Basic Controlled Components

ControlledInput.jsx
import React, { useState } from 'react';
function ControlledInput() {
const [name, setName] = useState('');
const handleChange = (event) => {
setName(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
alert(`Hello, ${name}!`);
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input
type="text"
value={name}
onChange={handleChange}
placeholder="Enter your name"
/>
</label>
<button type="submit">Submit</button>
</form>
);
}
export default ControlledInput;

In this example, the name input is controlled by React’s useState hook. Each change re-renders the form, reflecting the latest input.

Multiple Controlled Inputs

For forms with multiple inputs, you can use a single state object to manage all form fields:

MultipleInputs.jsx
import React, { useState } from 'react';
function MultipleInputForm() {
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
subscribe: false
});
const handleChange = (event) => {
const { name, value, type, checked } = event.target;
setFormData(prevData => ({
...prevData,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('Form Data:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
First Name:
<input
type="text"
name="firstName"
value={formData.firstName}
onChange={handleChange}
/>
</label>
</div>
<div>
<label>
Last Name:
<input
type="text"
name="lastName"
value={formData.lastName}
onChange={handleChange}
/>
</label>
</div>
<div>
<label>
Email:
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</label>
</div>
<div>
<label>
<input
type="checkbox"
name="subscribe"
checked={formData.subscribe}
onChange={handleChange}
/>
Subscribe to newsletter
</label>
</div>
<button type="submit">Submit</button>
</form>
);
}
export default MultipleInputForm;

This example shows how to handle multiple form inputs with a single state object and change handler.

Uncontrolled Components

Uncontrolled components store their own state internally and use refs to access their values when needed. This approach is closer to traditional HTML forms.

UncontrolledForm.jsx
import React, { useRef } from 'react';
function UncontrolledForm() {
const nameRef = useRef(null);
const emailRef = useRef(null);
const messageRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
const formData = {
name: nameRef.current.value,
email: emailRef.current.value,
message: messageRef.current.value
};
console.log('Form Data:', formData);
// Reset form
nameRef.current.value = '';
emailRef.current.value = '';
messageRef.current.value = '';
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Name:
<input
type="text"
ref={nameRef}
defaultValue=""
placeholder="Enter your name"
/>
</label>
</div>
<div>
<label>
Email:
<input
type="email"
ref={emailRef}
defaultValue=""
placeholder="Enter your email"
/>
</label>
</div>
<div>
<label>
Message:
<textarea
ref={messageRef}
defaultValue=""
rows="4"
placeholder="Enter your message"
/>
</label>
</div>
<button type="submit">Submit</button>
</form>
);
}
export default UncontrolledForm;

Uncontrolled components use refs to access form values and defaultValue instead of value.

Form Validation

Form validation ensures data integrity and provides user feedback. Here are different approaches to validation in React forms.

Basic Validation

BasicValidation.jsx
import React, { useState } from 'react';
function BasicValidationForm() {
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({});
const validateForm = () => {
const newErrors = {};
// Email validation
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
if (!formData.email) {
newErrors.email = 'Email is required';
} else if (!emailRegex.test(formData.email)) {
newErrors.email = 'Please enter a valid email';
}
// Password validation
if (!formData.password) {
newErrors.password = 'Password is required';
} else if (formData.password.length < 6) {
newErrors.password = 'Password must be at least 6 characters';
}
// Confirm password validation
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'Passwords do not match';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (event) => {
const { name, value } = event.target;
setFormData(prev => ({
...prev,
[name]: value
}));
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
const handleSubmit = (event) => {
event.preventDefault();
if (validateForm()) {
console.log('Form is valid:', formData);
alert('Form submitted successfully!');
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Email:
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
style={{ borderColor: errors.email ? 'red' : '' }}
/>
</label>
{errors.email && <span style={{ color: 'red' }}>{errors.email}</span>}
</div>
<div>
<label>
Password:
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
style={{ borderColor: errors.password ? 'red' : '' }}
/>
</label>
{errors.password && <span style={{ color: 'red' }}>{errors.password}</span>}
</div>
<div>
<label>
Confirm Password:
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
style={{ borderColor: errors.confirmPassword ? 'red' : '' }}
/>
</label>
{errors.confirmPassword && <span style={{ color: 'red' }}>{errors.confirmPassword}</span>}
</div>
<button type="submit">Register</button>
</form>
);
}
export default BasicValidationForm;

This example shows client-side validation with custom error handling and real-time feedback.

Using Formik

Formik is a popular library that simplifies form management in React:

FormikExample.jsx
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
function FormikForm() {
return (
<div>
<h2>Sign Up Form</h2>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
}}
validate={values => {
const errors = {};
if (!values.email) {
errors.email = 'Required';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
if (!values.firstName) {
errors.firstName = 'Required';
}
if (!values.lastName) {
errors.lastName = 'Required';
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<div>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" component="div" style={{ color: 'red' }} />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" component="div" style={{ color: 'red' }} />
</div>
<div>
<label htmlFor="email">Email Address</label>
<Field name="email" type="email" />
<ErrorMessage name="email" component="div" style={{ color: 'red' }} />
</div>
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
);
}
export default FormikForm;

Formik handles form state, validation, and submission with minimal boilerplate code.

Using React Hook Form

React Hook Form is a performant library with minimal re-renders:

ReactHookForm.jsx
import React from 'react';
import { useForm } from 'react-hook-form';
function ReactHookFormExample() {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm();
const onSubmit = (data) => {
console.log('Form Data:', data);
alert('Form submitted successfully!');
};
const watchedPassword = watch('password');
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>
First Name:
<input
type="text"
{...register('firstName', {
required: 'First name is required',
maxLength: {
value: 20,
message: 'First name cannot exceed 20 characters'
}
})}
/>
</label>
{errors.firstName && <span style={{ color: 'red' }}>{errors.firstName.message}</span>}
</div>
<div>
<label>
Email:
<input
type="email"
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i,
message: 'Invalid email address'
}
})}
/>
</label>
{errors.email && <span style={{ color: 'red' }}>{errors.email.message}</span>}
</div>
<div>
<label>
Password:
<input
type="password"
{...register('password', {
required: 'Password is required',
minLength: {
value: 6,
message: 'Password must be at least 6 characters'
}
})}
/>
</label>
{errors.password && <span style={{ color: 'red' }}>{errors.password.message}</span>}
</div>
<div>
<label>
Confirm Password:
<input
type="password"
{...register('confirmPassword', {
required: 'Please confirm your password',
validate: (value) =>
value === watchedPassword || 'Passwords do not match'
})}
/>
</label>
{errors.confirmPassword && <span style={{ color: 'red' }}>{errors.confirmPassword.message}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}
export default ReactHookFormExample;

React Hook Form provides excellent performance with built-in validation and minimal re-renders.

Best Practices

Form State Management

  • Use controlled components for forms that need real-time validation or formatting
  • Use uncontrolled components for simple forms where you only need the final values
  • Consider form libraries like Formik or React Hook Form for complex forms

Validation Strategies

  • Client-side validation for immediate user feedback
  • Server-side validation for security and data integrity
  • Schema-based validation using libraries like Yup for consistent rules

Performance Optimization

  • Debounce validation for expensive operations
  • Use React.memo for form components that don’t need frequent re-renders
  • Minimize re-renders by using libraries like React Hook Form

Key Takeaways

  • Controlled components give you full control over form state and enable real-time features
  • Uncontrolled components are simpler but offer less control
  • Form validation should be implemented on both client and server sides
  • Form libraries can significantly reduce boilerplate code for complex forms
  • Performance considerations are important for large forms with many inputs

Forms in React can range from simple to complex, but understanding these fundamental concepts will help you choose the right approach for your specific needs.