An Introduction to Validation in React with Yup
By: Ross Bulat
Tags: JavaScript, Programming, Developer, React, ES6

An Introduction to Validation in React with Yup

The JavaScript Object Schema Validator and Object Parser to use with React

In this article we will visit why you should be using Yup in your React projects, what it does exactly, and how to make the most out of the package to simplify your code, with examples.

Yup is designed for front-end browser based use. If you are interested in server-side object schema validation with NodeJS, take a look at my article on Joi:

Why should you care about Yup?

Yup makes our lives far easier for validating the data our apps consume and manipulate, in an abstract way that does not interfere with the rest of our business logic.

Web apps built on JavaScript Frameworks are now highly interactive and mutable in and of themselves; that is what the end user has come to expect. With this comes a large amount of data exchange, whether it be from API requests, form submissions, or custom objects to handle our state.

We need to make sure we are delivering data that our components expect to work with, otherwise we will be dealing with unintended consequences in the form of failed component renders or app crashes — we don’t want this.

The general gist of Yup

So how does Yup work exactly?

With Yup, we create a Yup formatted object that resembles our intended schema for an object, and then use Yup utility functions to check if our data objects match this schema — hence validating them.

A Schema Object

Let’s use form validation as a use case for our Yup example.

Note: Form validation is by no means the only use case for Yup — use it to validate any object that you need to, whether you are retrieving data from your APIs or an external API library, or simply working with multiple objects or arrays within your project that need to adhere to a particular schema.

With that said, now imagine I am creating a checkout address form — which may resemble the following structure:

{

email_address: email <required>

full_name: string, <required>

house_no: int,

address_1: string <required>,

address_2: string,

post_code: string <required>

timestamp: date <required>

}

This is the type of data I am expecting from this form, or my schema for a particular object. If my object does not match this schema, it is not valid.

Notice that I have treated the email_address field as an email data type. This is not an error. Yup can test whether a value is an email address with one method call . Likewise with our timestamp value — Yup can test whether this value is a date.

What does this look like as a Yup schema object? Something like this:

const checkoutAddressSchema = yup.object().shape({ email_address: yup

.string()

.email()

.required(),

full_name: yup

.string()

.required(),

house_no: yup

.number()

.required()

.positive()

.integer(),

address_1: yup

.string()

.required(),

address_2: yup

.string(),

post_code: yup

.string()

.required(),

timestamp: yup

.date()

.default(() => (new Date()),

}), });

A Yup schema is an immutable object responsible for validating an object.

This code is self explanatory: We can see for ourselves the responsibility of this object in a detailed way without having to add a single comment.

Each validator has its own method. This granularity allows us as developers to combine as many validators as we see fit for our data validation.

Data types such as string , integer , boolean , array , object and date are all callable validators within Yup, making it simple for Yup newcomers to begin using the code.

However, Yup goes way beyond simple data types. In the above example we also use validators like email , required , and positive , allowing me to check whether a value is representative of an email address, whether a value exists and whether a number is positive respectively. (we also have notRequired to use for greater readability of your schema).

This is great, abstract, reusable design. We can even assign default values with an embedded function, as the above example demonstrates with the timestamp field:

...

timestamp: yup

.date()

.default(() => (new Date()),

}

...

default() can be anything you wish to assign: it takes any value. The function can be expanded and treated like any other to include any logic you need to process in order to generate a default value:

...

timestamp: yup

.date()

.default(() => {

//check for stuff

return new Date();

}),

}

...

Validating Objects with My Schema

Now we have a schema for our checkout address form, we can then use it to validate the data we receive from it. Let’s say I receive the following data:

let addressFormData = {

email_address: 'ross@jkrbinvestments.com',

full_name: 'Ross Bulat',

house_no: null,

address_1: 'My Street Name',

post_code: 'AB01 0AB',

}

As you can see, the address_2 and timestamp fields are missing from here — but they are not required in our schema, so the object will still be valid.

From here I would like to demonstrate 3 things:

Check if my object is valid . So a true or false result of whether my object is indeed a valid checkout address object.

. So a true or false result of whether my object is indeed a valid checkout address object. Validate the object . E.g. use my schema to fill in any default values. This also type casts our object.

. E.g. use my schema to fill in any default values. This also type casts our object. Visit the cast() method of Yup, that type casts an object without validating it.

Checking if the object is valid

To do this, we use yup.isValid() , which will look like the following when applied to our example:

const valid = await checkoutAddressSchema.isValid(addressFormData);

isValid() returns a promise, so we can either treat this with async and await as demonstrated above, or use callback syntax:

checkoutAddressSchema

.isValid(addressFormData)

.then(function(valid) {



//valid - true or false

});

I prefer using await for cleaner syntax.

Validating the Object

In most cases we will require actually validating the object: process it to adhere to your schema expectations. In our example, we are expecting a timestamp value, and we have a default value if it is missing.

Let’s validate our address form with the following:

await checkoutAddressSchema.validate(addressFormData);

Now our object will be suitable for further processing.

Casting an Object

With the above example, validate() , we are checking whether our data is valid, and we are running validation on it. This is a handy shortcut that you may find you use the majority of time. However, we could just run an object through a cast() method that checks and fixes its types.

With our checkout address form, we may have a string that represents our house_no , whereas my schema expects an integer. cast() will fix this. It would also convert a timestamp string into our Date object if we indeed provided a timestamp string.

Cast is called like so:

checkoutAddressSchema.cast(addressFormData);

Indispensable Tool?

At this point it is becoming clear that Yup adds an extra vital layer of data security to our apps in a concise way. Validation becomes much less of a headache, and actually a joy to write and read because of the simplicity of the syntax and abstraction of the Yup library.

To expand your initial understanding of Yup, let’s visit some other useful methods available within the package.

Miscellaneous Useful Methods

Remember our form data structure outlined at the top of the article? This can be retrieved from a Yup object with the describe() method. describe() collects the object schema details and serialises them into a separate object.

method. collects the object schema details and serialises them into a separate object. Want to combine 2 schemas into one? Use the concat() method. Perhaps you are combining multiple objects into one API call body, and therefore want to validate the entire body with one Yup object. concat() will come in handy for this.

method. Perhaps you are combining multiple objects into one API call body, and therefore want to validate the entire body with one Yup object. will come in handy for this. Aborting validation early. By default Yup runs through an entire validation process and collects errors as it runs, and returns them all once validation is complete. We may not want to do this. We can use abortEarly to stop validation execution as soon as the first error crops up. abortEarly is passed as an options argument within validate() :

await checkoutAddressSchema.validate(addressFormData, { abortEarly: false })