Form Validation in React
Handling user inputs is the key to building interactive web applications in React.
Forms are a very common and valuable tool for collecting user information. They can be used for authentication, registration, feedback, or to collect other important information.
In this article, we’ll talk about form validation - verifying user inputs to improve the quality of collected data. First, we’ll implement basic form validation from the ground up. Then we’ll explore Formik, a great library for building smart forms in React.
By the time you’re finished reading, you’ll be able to create forms and validate them easily.
Find guides similar to this one on SimpleFrontEnd.
Table of Contents
Form Validation in React
React has a large community of developers who create libraries for important features like form validation. There are numerous great libraries for easy form validation in React, so typically, you won’t need to write all the code yourself.
We’re still going to explain how to implement basic form validation in React from scratch. Understanding low-level code can help you make better use of form validation libraries, which follow the same principles but add many great features on top of the basic functionality.
If you’re looking for a short answer, skip to the section where we discuss using Formik to validate forms in React.
State and controlled components in React
React components often have a state object, which stores essential information (data) to represent the most recent state of the component. React state often contains user inputs, so you can also say that it represents the most recent choices and inputs of the user.
React docs actually recommend storing user inputs in the state, which is a normal JavaScript object. So you can write a JavaScript function to verify user inputs and, if necessary, display helpful feedback to help users fix their mistakes.
In React, <input>
components that get their value from the state are called controlled components.
Any time a user types or deletes something from the field, the onChange
event handler will update the state, and the input field will show the updated value.
Implement form validation in React
User inputs are much easier to validate when stored in the state.
Create state variables
We’ll assume you’re already familiar with initializing and using state in React and not bore you with a detailed explanation.
For our practical example, let’s create three state variables:
const [inputFields, setInputFields] = useState({
email: "",
password: "",
age: null
});
const [errors, setErrors] = useState({});
const [submitting, setSubmitting] = useState(false);
inputFields
object has three properties to store users’ input: email
, password
, and age
.
One for each field of the form we’re going to build.
When users enter something into one of the three fields, we’ll update the corresponding property of the inputFields
object to reflect users’ most recent input.
errors
object will store information about users’ errors.
The submitting
variable will store a boolean that indicates whether the user is attempting to submit the form.
Function for validation
Next, let’s create a function to validate users’ inputs:
const validateValues = (inputValues) => {
let errors = {};
if (inputValues.email.length < 15) {
errors.email = "Email is too short";
}
if (inputValues.password.length < 5) {
errors.password = "Password is too short";
}
if (!inputValues.age || inputValues.age < 18) {
errors.age = "Minimum age is 18";
}
return errors;
};
Our validation function takes one argument - the object representing current input values.
In the function body, we create an object to store any possible errors.
First, we check the length of email
value.
If it’s under 15 characters long, we set the email
property of the errors
object to a custom error message.
Second, we check the length of the password
value.
If it’s under five characters long, we set the password
property of the errors
object to a custom error message.
Then we evaluate the age
value.
If it’s NULL (its initial value) or under 18, we set the age
property of the errors
object to a custom error message.
Finally, we return the errors
object, which should contain the result of validation.
onChange handler
Next, we need an event handler to update the state every time a user enters (or deletes) values in the field.
const handleChange = (e) => {
setInputFields({ ...inputFields, [e.target.name]: e.target.value });
};
onSubmit handler
We also need to define what happens after the user submits the form.
Our handleSubmit
function will use the preventDefault()
method to stop the page from reloading.
The function also updates the errors
state variable to the result of validation.
Finally, handleSubmit
will run only if the user tries to submit the form, so we set submitting
boolean to true.
const handleSubmit = (event) => {
event.preventDefault();
setErrors(validateValues(inputFields));
setSubmitting(true);
};
Supporting functions
Before we proceed, we want to make sure there are no errors in the submitted data.
So we define the finishSubmit()
function, executed only if two conditions are met: errors
object has no properties or values, and submitting
is set to true
.
useEffect()
runs every time there’s a change to the errors
state variable.
If the state of errors
does change, we need to re-evaluate whether the form should be submitted.
You can also use the finishSubmit()
function to clear the form after submit in React.
const finishSubmit = () => {
console.log(inputFields);
};
useEffect(() => {
if (Object.keys(errors).length === 0 && submitting) {
finishSubmit();
}
}, [errors]);
Create the form
As a final step, let’s define React elements and the JSX structure of our form.
Let’s create a <form>
element with three pairs of <label>
and <input>
elements.
<div className="App">
<form>
<div>
<label for="email">Email</label>
<input
type="email"
name="email"
value={inputFields.email}
onChange={handleChange}
></input>
<label for="password">Password</label>
<input
type="password"
name="password"
value={inputFields.password}
onChange={handleChange}
></input>
<label for="password">Age</label>
<input
type="number"
name="age"
value={inputFields.age}
onChange={handleChange}
></input>
</div>{" "}
<button type="submit">Submit Information</button>
</form>
</div>
Each <input>
element must have type
, name
, value
, and onChange
attributes.
You can work with a live demo in CodeSandbox.
Finish implementing form validation in React
First let’s set the form’s onSubmit
attribute to the handler we previously defined.
<div className="App">
<form onSubmit={handleSubmit}>
<div>
<label for="email">Email</label>
<input
type="email"
name="email"
value={inputFields.email}
onChange={handleChange}
></input>
<label for="password">Password</label>
<input
type="password"
name="password"
value={inputFields.password}
onChange={handleChange}
></input>
<label for="password">Age</label>
<input
type="number"
name="age"
value={inputFields.age}
onChange={handleChange}
></input>
</div>
<button type="submit">Submit Information</button>
</form>
</div>
Second, let’s display a message when the form is successfully submitted.
The text should probably display on top of the form, so we should add this code right before the <form>
element.
<div className="App">
{Object.keys(errors).length === 0 && submitting ? (
<span className="success">Successfully submitted ✓</span>
) : null}
<form onSubmit={handleSubmit}>
<div>
<label for="email">Email</label>
<input
type="email"
name="email"
value={inputFields.email}
onChange={handleChange}
></input>
<label for="password">Password</label>
<input
type="password"
name="password"
value={inputFields.password}
onChange={handleChange}
></input>
<label for="password">Age</label>
<input
type="number"
name="age"
value={inputFields.age}
onChange={handleChange}
></input>
</div>
<button type="submit">Submit Information</button>
</form>
</div>
Once again, the success message will only display if there are no errors and if submitting
is true.
Third, let’s add error messages that only render if there are errors.
For email, the code would look something like this:
{errors.email ? (
<p className="error">
Email should be at least 15 characters long
</p>
) : null}
A simple ternary operator that returns a paragraph of text if there’s an error, and null
(nothing) if there isn’t.
The same principle applies to password field:
{errors.password ? (
<p className="error">
Password should be at least 5 characters long
</p>
) : null}
And a custom message for age
errors:
{errors.age ? <p className="error"> Minimum age is 18</p> : null}
Live demo
You can try out the form yourself and play around with the source code on CodeSandbox.
Form Validation Library
React developers can use Formik to validate forms and display helpful error messages if needed.
What is Yup?
Yup is a schema creator and a very useful tool for form validation. You can use it to validate input values by their type, minimum and maximum length, requiredness, and even specify custom error messages for each type of error.
Formik allows you to use Yup as well as normal JavaScript functions for validation. Yup is the most useful for setting up a complex set of rules.
Using Formik for Form Validation in React
Formik library keeps track of your form’s state and provides custom components for easy form validation in React.
<Formik>
is the main custom component that gives us access to methods and values like current input values and errors.
It can be used with standard <input>
and <form>
elements, as well as custom Form
, Field
, and ErrorMessage
components from Formik
library.
To get started, install Formik and import the main component.
import { Formik } from "formik"
Let’s use the <Formik>
component with standard input elements first.
Our form has the same three fields as before - email
, password
and age
.
Previously we initialized form values by passing an argument to the useState()
hook.
With <Formik>
, we initialize form values by setting the initialValues
prop.
Let’s create an object that holds initial values.
const initialValues = {
email: "",
password: "",
age: null
}
Object property names must match values of the name
attribute on the input field.
<input type="text" name="email"></input>
<input type="text" name="password"></input>
<input type="number" name="age"></input>
Next, let’s write a simple function to validate input values, record errors in the object, and return it.
function validate(values) {
let errors = {};
if (inputValues.email.length < 15) {
errors.email = "Email is too short";
}
if (inputValues.password.length < 5) {
errors.password = "Password is too short";
}
if (!inputValues.age || inputValues.age < 18) {
errors.age = "Minimum age is 18";
}
return errors;
}
Property names of the errors
object must match the values of the name
attribute on the input field.
Formik will take care of the rest.
We’ll also show how to use Yup to set up a schema for validation.
Last, we define the submitForm
function.
It’s supposed to be called once the form is validated and values are to be submitted.
const submitForm = (values) => {
console.log(values);
};
To keep things simple, we’ll simply log submitted values to the console.
JSX structure of the component
Now, let’s define component UI in JSX.
In the return statement, the form is wrapped with a custom <Formik>
component, which has three attributes: initialValues
, validate
, and onSubmit
.
In the previous step, we defined all three - initialValues
object as well as validate and submitForm functions.
Between the opening and closing tags of the <Formik>
custom component, we define a render function that returns the JSX structure of the form.
const SignIn = () => {
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={submitForm}
>
{(props) =>
return (
<form onSubmit={props.handleSubmit}>
<label htmlFor="username">Enter username:</label>
<input
type="text"
name="username"
value={props.values.username}
onChange={props.handleChange}
className={props.errors.username ?
"highlight-error" : null}
/>
{props.errors.username && (<p className="error-text">{props.errors.username}</p>)}
<input
type="password"
name="password"
value={props.values.password}
onChange={props.handleChange}
className={props.errors.password ?
"highlight-error" : null}
/>
{props.errors.password && (<p className="error-text">{props.errors.password}</p>)}
<button {props.isValid? “” : submit}>Submit</button>
</form>
)
}
</Formik>
)
}
We use render props to access methods and values essential for form validation.
For example, we use the props.handleSubmit
method to handle submit events on the <form>
element.
Errors found during the validation process will be stored in the errors
object.
For example, we evaluate props.errors.username
to conditionally customize the appearance of the input field or display an error message only if there’s an error.
props.handleChange
method is set as the onChange
event handler for each input field.
We can access the current values of input fields by reading props.values
.
props.isValid
is a boolean value to indicate whether the form is successfully validated.
It’s true
if there are no errors and false
if there are.
We can use it to conditionally disable or enable the submit button.
There are other render props, such as: touched
, handleBlur
, and dirty
values, but we’re not going to use them.
Check out the API reference to learn more about these props.
Create validation schema with Yup
Let’s explore Yup and how it saves us a lot of time on writing validation.
const schema = Yup.object().shape({
username: Yup.string().required('please enter username')
password: Yup.string().required('please enter password').min(4, 'password must be at least four characters long')
})
We use a chain of methods to set up a custom condition.
The string()
method specifies the type of value.
The required()
method specifies that the value is required.
You can set a custom error message by passing a string as an argument to the method.
Validation rules for password
values are similar to those of username
.
There’s also a third min()
method with two arguments - minimum number of characters and custom error message if the minimum character is not met.
Instead of a validate
function, we’ll use this basic schema to validate the form.
Read docs to learn about setting more complex rules.
If you’re going to use Yup schema for validation, use validationSchema
prop on the <Formik>
component.
<Formik
initialValues={initialValues}
validationSchema={schema}
onSubmit={submitForm}
>
{/* input fields and error messages */}
</Formik>
Using Formik with custom input components
Now let’s look at custom components from the Formik library.
Custom Form
Field
, ErrorMessage
components for simpler form validation in React.
Form
custom component is bound with the main Formik component.
For example, <form>
React element requires an explicit onSubmit
attribute, but <Form>
does not.
const SignIn = () => {
return (
<Formik
initialValues={initialValues}
validationSchema={schema}
onSubmit={submitForm}
>
{(props) =>
return (
<form onSubmit={props.handleSubmit}>
{/* input fields */}
</form>
)
}
</Formik>
)
}
The <Form>
custom component is automatically linked with the main <Formik>
component.
There is no need to explicitly set the onSubmit
value.
const SignIn = () => {
return (
<Formik
initialValues={initialValues}
validationSchema={schema}
onSubmit={submitForm}
>
{(props) =>
return (
<Form>
{/* input fields */}
</Form>
)
}
</Formik>
)
}
Next, let’s discuss individual input fields.
Standard <input>
elements require the onChange
attribute.
const SignIn = () => {
return (
<Formik
initialValues={initialValues}
validationSchema={schema}
onSubmit={submitForm}
>
{(formik) =>
return (
<Form>
<input
type="text"
name="username"
value={formik.values.username}
onChange={formik.handleChange}
className={formik.errors.username ?
"highlight-error" : null}
/>
{formik.errors.username && (<p className="error-text">{formik.errors.username}</p>)}
</Form>
)
}
</Formik>
)
}
Field
and ErrorMessage
custom components do not require explicit value
or onChange
attributes.
Formik looks at the name
attribute to automatically update values
and errors
objects without an explicit onChange
handler, which results in cleaner syntax.
These components do not require an explicit value
attribute for the same reason.
Formik maintains the form state, so it looks at the name
attribute to get relevant values from the state.
const SignIn = () => {
return (
<Formik
initialValues={initialValues}
validationSchema={schema}
onSubmit={submitForm}
>
{(formik) =>
return (
<Form>
<Field
type="text"
name="username"
/>
{formik.errors.username && (<p className="error-text">{formik.errors.username}</p>)}
</Form>
)
}
</Formik>
)
}
Finally, let’s look at the ErrorMessage
custom component.
Previously, we had to manually check if formik.errors.username
is true
to display the error message.
ErrorMessage
custom component is much easier to use.
It only renders if the current value is invalid.
ErrorMessage
also allows you to specify the underlying HTML element.
For example, specify whether the error message should be a <p>
or <span>
element.
You can also set the custom className
value to customize the appearance of the error message.
Change the text color to red, for example.
const SignIn = () => {
return (
<Formik
initialValues={initialValues}
validationSchema={schema}
onSubmit={submitForm}
>
{(formik) =>
return (
<Form>
<Field
type="text"
name="username"
/>
<ErrorMessage
name="email"
component="span"
className="error"
/>
</Form>
)
}
</Formik>
)
}
Conclusion
User inputs are unpredictable, so most web applications require form validation to manage them. In this article, we explained concepts like state and controlled components to help you better understand form validation in React.
We also walked you through two different ways to implement form validation in React. First, we showed how to implement basic form validation without external libraries. Then we explored Formik library and its use for comprehensive form validation in React.
Visit SimpleFrontEnd to read more guides similar to this one.