Reusable React Code Modules, Part 5 - Forms and Validation
Streamlining User Input: Creating Reusable Form and Validation Components
Forms and validation are integral to web applications, ensuring data integrity and enhancing user experience. This guide covers how to structure reusable form and validation modules in React. We'll explore practical examples, recommended libraries, and code snippets.
Common Libraries and Tools
Formik
React Hook Form
Final Form
Final Form
1. Formik
Formik is a popular library for managing forms in React
It simplifies form handling and validation
Key Features
Easy form state management
Validation schema support (Yup)
Minimal boilerplate
2. React Hook Form
React Hook Form leverages React Hooks for managing form state
There are no additional libraries associated with this approach
Key Features
Minimal re-renders
Integrates with existing form controls
Async validation support
3. Redux Form
Redux Form integrates with Redux, allowing you to manage form state globally
Redux is a third-party library that you must install
Key Features
Form state in Redux store
Synchronous and asynchronous validation
Field-level validation
4. Final Form
Final Form is a framework-agnostic form library, also available as React Final Form
Final Form is a third-party library that you must install
Key Features
Subscription-based form state
Flexible validation
Lightweight
Comparison
Formik: Best for forms requiring complex validation and state management with minimal boilerplate
React Hook Form: Ideal for high-performance applications due to minimal re-renders
Redux Form: Suitable for applications where form state needs to be globally accessible via Redux
Final Form: Best for lightweight and flexible form management without framework dependency
Examples
Example 1: Formik
Login Form:
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const LoginSchema = Yup.object().shape({
email: Yup.string().email('Invalid email').required('Required'),
password: Yup.string().min(6, 'Too Short!').required('Required'),
});
const LoginForm = () => (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema}
onSubmit={values => {
console.log(values);
}}
>
{() => (
<Form>
<div>
<label htmlFor="email">Email</label>
<Field name="email" type="email" />
<ErrorMessage name="email" component="div" />
</div>
<div>
<label htmlFor="password">Password</label>
<Field name="password" type="password" />
<ErrorMessage name="password" component="div" />
</div>
<button type="submit">Login</button>
</Form>
)}
</Formik>
);
export default LoginForm;
Example 2: React Hook Form
Registration Form:
import React from 'react';
import { useForm } from 'react-hook-form';
import * as Yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
const schema = Yup.object().shape({
username: Yup.string().required('Username is required'),
email: Yup.string().email('Invalid email').required('Required'),
password: Yup.string().min(6, 'Too Short!').required('Required'),
});
const RegistrationForm = () => {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema)
});
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="username">Username</label>
<input {...register('username')} />
<p>{errors.username?.message}</p>
</div>
<div>
<label htmlFor="email">Email</label>
<input {...register('email')} />
<p>{errors.email?.message}</p>
</div>
<div>
<label htmlFor="password">Password</label>
<input {...register('password')} type="password" />
<p>{errors.password?.message}</p>
</div>
<button type="submit">Register</button>
</form>
);
};
export default RegistrationForm;
Example 3: Redux Form
Contact Form:
import React from 'react';
import { Field, reduxForm } from 'redux-form';
const validate = values => {
const errors = {};
if (!values.name) {
errors.name = 'Required';
}
if (!values.email) {
errors.email = 'Required';
} else if (!/^.+@.+$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
if (!values.message) {
errors.message = 'Required';
}
return errors;
};
const ContactForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name</label>
<Field name="name" component="input" type="text" />
</div>
<div>
<label htmlFor="email">Email</label>
<Field name="email" component="input" type="email" />
</div>
<div>
<label htmlFor="message">Message</label>
<Field name="message" component="textarea" />
</div>
<button type="submit">Submit</button>
</form>
);
export default reduxForm({
form: 'contact',
validate
})(ContactForm);
Example 4: Final Form
Subscription Form:
import React from 'react';
import { Form, Field } from 'react-final-form';
const validate = values => {
const errors = {};
if (!values.name) {
errors.name = 'Required';
}
if (!values.email) {
errors.email = 'Required';
} else if (!/^.+@.+$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
return errors;
};
const SubscriptionForm = () => (
<Form
onSubmit={formValues => console.log(formValues)}
validate={validate}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name</label>
<Field name="name" component="input" type="text" />
</div>
<div>
<label htmlFor="email">Email</label>
<Field name="email" component="input" type="email" />
</div>
<button type="submit">Subscribe</button>
</form>
)}
/>
);
export default SubscriptionForm;