Use Formik+Yup to create a multi-step React form part 2: validate with Yup and display errors

Image for post
Image for post

So the last article I explained how to create a simple form with Formik, in this article, I want to talk about how to validate with Yup and display errors on your form.

Custom Validation

In the last article’s code, you can see how we usually do validation:

const validate = values => {
const errors = {};
if (!values.firstName) {
errors.firstName = "Required";
} if (!values.lastName) {
errors.lastName = "Required";
}
return errors;
};
<Formik
initialValues={formData}
validate={validate}
>
// put a form here
</Formik>

The basic idea is that there is a validate prop in Formik . We can pass the validation method into it, which will take the values that the user inputs and validate it with your custom methods. In the end, errors will be returned and can be used to display errors messages.

However, very often we need to use the same validation for different apps. For example, if we want to validate the user first name is presented in another app, we have to go to our last app and copy codes there or write it all manually again. That’s annoying and very error-prone! Here’s where Yup comes as a handy tool.

Yup Validation

Schema

Yup is very useful because it embeds a lot of common validation. We can install it first:

npm install yup

And then import it in the document that we need to use validation like so:

import * as Yup from 'yup';

After this, we can freely use everything like so:

Yup.<data type>.<built-in method>

For example, to validate a string to be valid email format, do this:

email: Yup.string.email()

This will validate the email field to see if it is a valid email.

To validate a whole object, we can do this:

let schema = yup.object().shape({
firstName: yup.string().required(),
lastName: yup.string().required(),
}),
});

It means schema is a method to validate a whole object, only when all the fields meeting all the requirements then the validation will pass.

ValidationSchema

Alright, so how to use this schema to help us validate? Actually, Formik likes Yup so much that it offers an easy way to use this schema : pass it to the validationSchema prop in <Formik> like this:

<Formik
initialValues={formData}
onSubmit={handleSubmit}
validationSchema={schema}
>
//Put the form here
</Formik>

So right now the magic happens! Formik will use the SignupSchema to validate all the fields in the form. And if there are any invalid items, the returned errors object will contain the field name and a message like so:

name: "A name is required."

I custom this message by passing the message to require method in: Yup.string.required(“A name is required.”) . Just check the documentation to find out how to pass a custom validation method .

Display error messages

How to access the error messages

As described from last article, Formik is just a react component. Therefore, props can be passed in and passed out of the component. If we want to access the errors, we can extract it from the props Formik has to offer to its child component like this:

<Formik
initialValues={formData}
onSubmit={handleSubmit}
validationSchema={schema}
>
{({ handleSubmit, handleChange, handleBlur, values, errors, touched }) => (
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleChange}
onBlur={handleBlur}
value={values.firstName}
name="firstName"
/>
{errors.firstName &&
<div>
{errors.firstName}
</div>}
<button type="submit">Submit</button>
</form>
)}
</Formik>

As we can see, the child component to Formik here is the <form>. handleSubmit, handleChange, handleBlur, values, errors, touched are all passed to the <form> component to use. In this way, we can access errors that is returned by the validation method.

Render error messages conditionally

Now everything looks much better! However, there’s still one problem:

errors.firstName &&
<div>
{errors.firstName}
</div>}

From this, we can tell, the condition for displaying the error message is that errors.firstName exists. That makes sense in the case where we touched the firstName field but didn’t write anything — the validation would fail because it is required . But, what if in the very beginning, when the form is firstly loaded?

It is just loaded, we haven’t touched it yet, so it is empty! And since the validation loads when the form is loaded, so if we console.log the error now, we will see errors.firstName is true, which leads to a red error showing in the form.

But that’s not what we want right? We want it only shows up when it is touched and there is an error related to that field.

To achieve this, we can do:

touched.firstName && errors.firstName ? <div>{errors.firstName} : null

In this case, it will only shows the error when we touch it AND when there’s an error.

Note

One last important thing is that: if you want to use the touched prop, make sure you use the handleBlur in each field input that you will use touched like so:

{({ handleSubmit, handleChange, handleBlur, values, errors, touched }) => (
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={handleChange}
onBlur={handleBlur} // IMPORTANT!!
value={values.firstName}
name="firstName"
/>
{errors.firstName &&
<div>
{errors.firstName}
</div>}
<button type="submit">Submit</button>
</form>
)}

That’s how it knows which field has been touched.

END

Thanks for reading! Now a form with validation and errors displaying correctly. Next step, we will talk about how to implement a multi-step form. Stay in touch!

Resources:

Formik document

Yup document

Use Formik+Yup to create a multi-step React form part 1: build a simple form

Web Developer | Ruby | Rails | SQL | Sinatra | React | Redux | HTML&CSS | JavaScript| MERN LinkedIn:https://www.linkedin.com/in/yingqi-chen/

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store