React Form Validation With Formik And Yup
As developers, it is our job to ensure that when users interact with the forms we set up, the data they send across is in the form we expect.
In this article, we will learn how to handle form validation and track the state of forms without the aid of a form library. Next, we will see how the Formik library works. We’ll learn how it can be used incrementally with HTML input fields and custom validation rules. Then we will set up form validation using Yup and Formik's custom components and understand how Yup works well with Formik in handling Form validation. We will implement these form validation methods to validate a simple sign up form I have set up.
Note: This article requires a basic understanding of React.
Form Validation In React
On its own, React is powerful enough for us to be able to set up custom validation for our forms. Let’s see how to do that. We’ll start by creating our form component with initial state values. The following sandbox holds the code for our form:
Form validation without the use of a library
const Form = () => {
const intialValues = { email: "", password: "" };
const [formValues, setFormValues] = useState(intialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
}
With the useState
hook, we set state variables for the formValues
, formErrors
and isSubmitting
.
- The
formValues
variable holds the data the user puts into the input fields. - The
formErrors
variable holds the errors for each input field. - The
isSubmitting
variable is a boolean that tracks if the form is being submitted or not. This will betrue
only when there are no errors in the form.
const submitForm = () => {
console.log(formValues);
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
setFormErrors(validate(formValues));
setIsSubmitting(true);
};
const validate = (values) => {
let errors = {};
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
if (!values.email) {
errors.email = "Cannot be blank";
} else if (!regex.test(values.email)) {
errors.email = "Invalid email format";
}
if (!values.password) {
errors.password = "Cannot be blank";
} else if (values.password.length < 4) {
errors.password = "Password must be more than 4 characters";
}
return errors;
};
useEffect(() => {
if (Object.keys(formErrors).length === 0 && isSubmitting) {
submitForm();
}
}, [formErrors]);
Here, we have 4 form handlers and a useEffect
set up to handle the functionality of our form.
handleChange
This keeps the inputs in sync with theformValues
state and updates the state as the user types.validate
We pass in theformValues
object as a argument to this function, then based on theemail
andpassword
meeting the validation tests, theerrors
object is populated and returned.handleSubmit
Whenever the form is submitted, theformErrors
state variable is populated with whatever errors may exist using thesetFormErrors(validate(formValues))
method.useEffect
Here, we check if theformErrors
object is empty, and ifisSubmitting
istrue
. If this check holds true, then thesubmitForm()
helper is called. It has single dependency, which is theformErrors
object. This means it only runs when theformErrors
object changes.submitForm
: this handles the submission of the form data.
return (
<div className="container">
<h1>Sign in to continue</h1>
{Object.keys(formErrors).length === 0 && isSubmitting && (
<span className="success-msg">Signed in successfully</span>
)}
<form onSubmit={handleSubmit} noValidate>
<div className="form-row">
<label htmlFor="email">Email</label>
<input
type="email"
name="email"
id="email"
value={formValues.email}
onChange={handleChange}
className={formErrors.email && "input-error"}
/>
{formErrors.email && (
<span className="error">{formErrors.email}</span>
)}
</div>
<div className="form-row">
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
id="password"
value={formValues.password}
onChange={handleChange}
className={formErrors.password && "input-error"}
/>
{formErrors.password && (
<span className="error">{formErrors.password}</span>
)}
</div>
<button type="submit">Sign In</button>
</form>
</div>
);
Here, we pass in the handleChange
helper functions to the inputs’ onChange
attribute. We link the value of the inputs to the formValues
object, making them controlled inputs. From the React docs, controlled inputs are inputs whose values are controlled by React. An input-error style is applied if there are any errors related to that specific input field. An error message is conditionally displayed beneath each input if there are any errors related to that specific input field. Finally, we check if there are any errors in the errors object and if isSubmitting
is true. If these conditions hold true, then we display a message notifying the user that they signed in successfully.
With this, we have a fully functional and validated form set up without the aid of a library. However, a form library like Formik with the aid of Yup can simplify the complexities of handling forms for us.
What Are Formik And Yup?
Right from the docs:
“Formik is a small library that helps you with the 3 most annoying parts in handling forms:
- Getting values in and out of form state.
- Validation and error messages
- Handling form submission.
Formik is a flexible library. It allows you to decide when and how much you want to use it. We can control how much functionality of the Formik library we use. It can be used with HTML input fields and custom validation rules, or Yup and the custom components it provides. Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React.
Yup is a JavaScript object schema validator. While it has many powerful features, we’ll focus on how it helps us create custom validation rules so we don’t have to. This is a sample Yup object schema for a sign-up form. We’ll go into Yup and how it works in depth later in the article.
const SignUpSchema = Yup.object().shape({
firstName: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Firstname is required"),
lastName: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Lastname is required"),
phoneNumber: Yup.string()
.required("Phone number is required")
.matches(
/^([0]{1}|\+?[234]{3})([7-9]{1})([0|1]{1})([\d]{1})([\d]{7})$/g,
"Invalid phone number"
),
email: Yup.string().email().required("Email is required"),
password: Yup.string()
.required("Password is required")
.min(6, "Password is too short - should be 6 chars minimum"),
});
Formik, HTML Input Fields And Custom Validation Rules
The following sandbox holds the code for this form set up:
The first thing we have to do is install Formik.
npm i formik
Then we can go ahead to import it in the file where we’ll make use of it.
import { Formik } from "formik";
Before creating the component, we need to create an initialValues
and validate
object which we’ll pass as props to the Formik component when we set it up. initialValues
and validate
are code snippets, not normal words.
The decision to do this outside the component is not a technical one, but rather for readability of our code.
const initialValues = {
email: "",
password: ""
};
initialValues
: is an object that describes the initial values of the respective form fields. The name given to each key in the initialValues
must correspond with the value of the name of the input field we want Formik to watch.
const validate = (values) => {
let errors = {};
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
if (!values.email) {
errors.email = "Email is required";
} else if (!regex.test(values.email)) {
errors.email = "Invalid Email";
}
if (!values.password) {
errors.password = "Password is required";
} else if (values.password.length < 4) {
errors.password = "Password too short";
}
return errors;
};
validate
: this accepts a function that handles the form validation. The function accepts an object in the form of data values as an argument and validates each property in the object based on the rules defined. Each key in the values object must correspond with the name of the input field.
const submitForm = (values) => {
console.log(values);
};
onSubmit
: This handles what happens after the user submits. The onSubmit prop takes a callback function that will only run when there are no errors, meaning the user inputs are valid.
const SignInForm = () => {
return (
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={submitForm}
>
{(formik) => {
const {
values,
handleChange,
handleSubmit,
errors,
touched,
handleBlur,
isValid,
dirty
} = formik;
return (
<div className="container">
<h1>Sign in to continue</h1>
<form onSubmit={handleSubmit}>
<div className="form-row">
<label htmlFor="email">Email</label>
<input
type="email"
name="email"
id="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={errors.email && touched.email ?
"input-error" : null}
/>
{errors.email && touched.email && (
<span className="error">{errors.email}</span>
)}
</div>
<div className="form-row">
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
id="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
className={errors.password && touched.password ?
"input-error" : null}
/>
{errors.password && touched.password && (
<span className="error">{errors.password}</span>
)}
</div>
<button
type="submit"
className={dirty && isValid ? "" : "disabled-btn"}
disabled={!(dirty && isValid)}>
Sign In
</button>
</form>
</div>
);
}}
</Formik>
);
};
We pass in the initialValues
object, and the submitForm
and validate
functions we defined earlier into Formik’s initialValues
, onSubmit
and validate
props respectively.
Using the render props pattern, we have access to even more props the Formik API provides.
values
This holds the values of the user inputs.handleChange
This is the input change event handler. It is passed to the input field<input onChange={handleChange}>
. It handles the changes of the user inputs.handleSubmit
The form submission handler. It is passed into the form<form onSubmit={props.handleSubmit}>
. This fires the function passed into theonSubmit
prop whenever the form is submitted.errors
This object holds the validation errors that correspond to each input field, and is populated with the definitions we passed into the Yup object schema.touched
This is an object that watches if a form field has been touched. Each key corresponds to the name of the input elements and has a boolean value.handleBlur
This is theonBlur
event handler, and it is passed to the input field<input onBlur={handleBlur} />
. When the user removes focus from an input, this function is called. Without it, if there are any errors in the input when it loses focus, the errors will only display when the user tries to submit.isValid
Returnstrue
if there are no errors (i.e. theerrors
object is empty) andfalse
otherwise.dirty
This prop checks if our form has been touched or not. We can use this to disable our submit button when the form loads initially.
When the form is submitted, Formik checks if there are any errors in the errors
object. If there are, it aborts the submission and displays the errors. To display the span using HTML inputs, we conditionally render and style the error message of each respective input field if the field has been touched and there are errors for that field.
<button
type="submit"
className={!(dirty && isValid) ? "disabled-btn" : ""}
disabled={!(dirty && isValid)}>
Sign In
</button>
Also, we can add a visual cue to the button. The button is conditionally styled and disable it if there are errors in the errors
object using the isValid
and the dirty
props.
Validation Using Formik’s Components And Yup
This sandbox holds the final code for this setup.
npm i yup
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
We install Yup, import the Field
, Form
, and the ErrorMessage
components from Formik.
Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React. With that we can then go ahead to create the schema we’ll be using for the sign in form using Yup. Instead of creating custom validations for each possible input field, which can be tedious, depending on the number of fields there are, we can leave that to Yup to handle.
const SignInSchema = Yup.object().shape({
email: Yup.string().email().required("Email is required"),
password: Yup.string()
.required("Password is required")
.min(4, "Password is too short - should be 4 chars minimum"),
});
Yup works similarly to how we define propTypes
in React. We created an object schema with Yup’s object
function. We define the shape of the validation object schema and pass it into Yup’s shape()
method. The required()
method. This method takes a string as an argument, and this string will be the error message. that displays whenever a required field is left blank.
This schema has two properties:
- An
email
property that is a string type and is required. - A
password
property that is of number type but is not required.
We can chain validation is Yup as seen above. The properties of the schema object match the name of the input fields. The docs go into the different validation methods available in Yup.
const SignInForm = () => {
return (
<Formik
initialValues={initialValues}
validationSchema={signInSchema}
onSubmit={(values) => {
console.log(values);
}}
>
{(formik) => {
const { errors, touched, isValid, dirty } = formik;
return (
<div className="container">
<h1>Sign in to continue</h1>
<Form>
<div className="form-row">
<label htmlFor="email">Email</label>
<Field
type="email"
name="email"
id="email"
className={errors.email && touched.email ?
"input-error" : null}
/>
<ErrorMessage name="email" component="span" className="error" />
</div>
<div className="form-row">
<label htmlFor="password">Password</label>
<Field
type="password"
name="password"
id="password"
className={errors.password && touched.password ?
"input-error" : null}
/>
<ErrorMessage
name="password"
component="span"
className="error"
/>
</div>
<button
type="submit"
className={!(dirty && isValid) ? "disabled-btn" : ""}
disabled={!(dirty && isValid)}
>
Sign In
</button>
</Form>
</div>
);
}}
</Formik>
);
};
While using HTML input fields get the job done, Formik’s custom components make things even easier for us, and reduce the amount of code we have to write! What are these custom components Formik provides us?
Formik
We’ve been using this for a while now. This is required for the other components to be usable.Form
A wrapper that wraps the HTML<form/>
element. It automatically links theonSubmit
method to the form’s submit event.Field
In the background, this automatically links the form input’sonChange
,onBlur
andvalue
attributes to Formik’shandleChange
,handleBlur
, andvalues
object respectively. It uses the name prop to match up with the state and automatically keeps the state in sync with the input value. With this component, we can decide to display it as an input field we want using it’sas
property. For example, will render atextarea
. By default, it renders an HTML input field.ErrorMessage
It handles rendering the error message for its respective field based on the value given to the name prop, which corresponds to the<Field />
’s name prop. It displays the error message if the field has been visited and the error exists. By default, it renders a string is thecomponent
prop is not specified.
We pass the signInSchema
into Formik using the validationSchema
prop. The Formik team loves the Yup validation library so they created a specific prop for Yup called validationSchema
which transforms errors into objects and matches against their values and touched functions.
Conclusion
Users do not know or care how you handle form validation. However, for you the developer, it should be as painless a process as possible, and I believe Formik stands out as a solid choice in that regard.
We have successfully looked at some of the options available to us when validating forms in React. We have seen how Formik can be used incrementally, and how it pairs well with Yup in handling form validation.
Resources
- Formik Docs
- Yup Docs
- Validation with Yup