DEV Community

Cover image for Client-side object validation with Yup
Kristijan Pajtasev
Kristijan Pajtasev

Posted on • Originally published at Medium

Client-side object validation with Yup

Introduction

Typescript introduced many positive things in JavaScript. When used right, it can help with having cleaner code and reducing bugs. However, it mainly works on the compile time, which means it helps you when writing code. Not when running it. And sometimes, you want to verify the structure of data on run time. If you have something simple, like checking if some value is a string, it is quite easy, but if you are validating some more complex structure with different data types. That can get much more complicated. For that, there are libraries like Yup, which I am going to cover in the rest of this post.

Image description

Installation

Yup installation is quite simple. All you need to do is either add it to your package.json file or run the following command:

npm run install -S yup
Enter fullscreen mode Exit fullscreen mode

Basics

It all starts by defining schema, and yup comes with a whole range of built-in, flexible, options for basic types, and options to build more complex ones.

const schema = yup.object().shape({
  // object schema
});
Enter fullscreen mode Exit fullscreen mode

In the code above, the result is yup schema. The parameter of the shape function is an empty object, so it does not test anything, but this is where you would pass validation details for your data. For that, let's consider the next values and build a validation object for them.

const data = {
  firstName: 'john',
  lastName: 'doe',
  age: 25,
  email: 'john.doe@email.com',
  created: new Date(2021, 5, 5)
}
Enter fullscreen mode Exit fullscreen mode

Observing data, we can see that firstName, lastName, and email are strings, age is a number, and created is a date. Lucky for us, yup supports all those types, and shape objects could be defined like the following.

const schema = yup.object().shape({
  firstName: yup.string(),
  lastName: yup.string(),
  age: yup.number(),
  email: yup.string(),
  created: yup.date(),
});
Enter fullscreen mode Exit fullscreen mode

Once we have both data and schema defined, data can be tested. For that, our schema has an isValid function that returns a promise that resolves with Boolean status.

const valid = schema.isValid(data).then((valid) => {
  console.log(valid); // true
});
Enter fullscreen mode Exit fullscreen mode

Additional validation

The example above is ok. But what if we need additional validations? What if lastName is missing? And email is defined as a string but not any string is email. Also, how about age, maybe there is a minimum and maximum value? Again, yup has the option to support all those requirements. For example, let us consider the following example of invalid data.

const data = {
  lastName: "a",
  age: 80,
  email: "blob",
  created: "Some invalid string"
}
Enter fullscreen mode Exit fullscreen mode

In the above, this would be a valid object. However, we could test all the additional requirements by using the next schema.

const schema = yup.object().shape({
  firstName: yup.string().required(),
  lastName: yup.string()
                .required("Last name is required").min(2),
  age: yup.number()
          .required()
          .min(30)
          .max(50, "Value needs to be less than 50"),
  email: yup.string().required().email(),
  created: yup.date().required(),
});
Enter fullscreen mode Exit fullscreen mode

The first thing above you might notice is that all have .required() call. This defines that a field is required, and it accepts an optional parameter of string which would be used instead of the default message. Other interesting calls are min and max. These depend on the data type. For numbers, it is defining minimum or maximum number value, and for strings, it defines length requirements. Almost all additional functions do take custom error messages as optional extra parameters.

Errors

In the above example, we determined if the schema is valid. But what if it isn't? You would want to know some details about it. Error messages and fields that are invalid and this is in my opinion biggest issue of yup. There is a method called validate, but getting fields and errors is not so straightforward from the documentation. Validate is an async function that returns a promise and resolves if valid, or rejects if invalid data. This can be caught by using two ways below (await is also an option and there are synchronized versions of these functions as well).

schema.validate(data).then(
  () => { /* valid data function */ },
  () => { /* invalid data function */ }
);

schema.validate(data).catch(errors => {
  // validation failed
});
Enter fullscreen mode Exit fullscreen mode

The problem mentioned above is that yup exits when it finds the first error, so it won't register all fields that do have it. But for that, we could pass the options object with the abortEarly field set to false. And this brings us to the next problem, which is getting data out of the error. In the error function, the parameter we get is an errors object which has inner property, and that property has a value of an array containing all errors, and as part of each error, we get a path property containing the field name and errors containing all error messages for that specific error.

schema.validate(invalidData, { abortEarly: false }).catch(function(errors) {
  errors.inner.forEach(error => {
    console.log(error.path, error.errors)
  })
});
Enter fullscreen mode Exit fullscreen mode

Nested data types

These above were all simple fields. But yup also contains complex ones like nested objects and arrays. I won't go into them too much, but you can see simple examples below.

let numberSchema = yup.array().of(yup.number().min(2));
let nestedObjectSchema = yup.object().shape({
  name: yup.string(),
  address: yup.object().shape({
    addressLine1: yup.string(),
    addressLine2: yup.string(),
    town: yup.string(),
    country: yup.string(),
  })
})
Enter fullscreen mode Exit fullscreen mode

Wrap up

Yup is a great library with many uses. And there are many more options than shown in this post. But I hope from this post you got some basic intro into understanding it, and all examples you can find also in my GitHub repository.


For more, you can follow me on Twitter, LinkedIn, GitHub, or Instagram.

Top comments (2)

Collapse
 
rahul488 profile image
rahul488

I don't want hard code the min Max value in schema, is there any way to get it from the field itself ?

Collapse
 
hi_iam_chris profile image
Kristijan Pajtasev

What do you mean by from the field itself?

If you dont want it hard-coded, you could exclude schema in the generator function, and pass such value as parameter

const  getSchema = (min, max) => {
  return  yup.object().shape({
                age: yup.number()
                              .required()
                              .min(min)
                              .max(max, "Value needs to be less than 50"),
  })
}

getSchema(30, 50)
Enter fullscreen mode Exit fullscreen mode