Form Validation in React and Beyond With Yup

Understanding the how to do form validation right in React and other frameworks with yup

in

Form Validation in React and Beyond with Yup

Ensuring that user submitted data in your web apps is almost always a tough challenge. Proper form validation helps prevent invalid data entry, reduces the risk of spam, and ultimately contributes to the integrity of your application’s data. While native JavaScript and custom solutions can handle basic validation needs, they often lead to cluttered code and limited reusability.

Yup, a versatile JavaScript library, addresses this challenge by offering a schema-based approach to form data validation. With Yup, developers can create reusable validation schemas that simplify the validation process, making code cleaner and easier to manage. Whether you’re working in React or any other framework, Yup provides a convenient solution that brings your form validation to a professional standard.

Understanding Yup: Core Concepts

Yup is a schema-based validation library that focuses on defining the structure and rules of your data. Unlike traditional validation methods which may involve scattered validation rules throughout your code, Yup enables you to centralize your validation logic into schemas, making it easier to manage and maintain.

At its core, Yup allows you to build validation schemas using an intuitive, object-oriented API. You start by defining the shape of the data you expect, specifying the types of each field, and setting rules that the data must adhere to. For instance, you can create a schema that requires a string to be an email with a valid email format, a number to fall within a specific range, or a field to be mandatory. Yup’s API includes methods such as shape, required, email, min, and max, which you can chain together to create complex validation logic.

Yup also offers advanced features like asynchronous validation, where you can perform server-side checks or other complex validations that require waiting for external data. Additionally, Yup supports nested schemas, allowing you to validate complex objects or arrays of data in a structured and organized way. With these capabilities, Yup enables developers to build validation strategies that are both expressive and maintainable.

Yup in React Applications

Yup integrates seamlessly with popular React form libraries like Formik and React Hook Form, making it an ideal choice for handling form validation in React applications. By combining Yup’s schema-based validation with these libraries, you can create user-friendly forms with strong input validation while keeping your codebase clean and maintainable.

Using Yup with Formik

Formik simplifies form management in React by handling form state, validation, and submission. To use Yup with Formik, you define your validation schema using Yup’s methods and pass it to the validationSchema prop in Formik. Here’s an example of how to set up a simple form with Formik and Yup:

  import React from 'react';
  import { Formik, Form, Field, ErrorMessage } from 'formik';
  import * as Yup from 'yup';

  // Define Yup validation schema
  const validationSchema = Yup.object().shape({
    name: Yup.string()
      .required('Name is required'),
    email: Yup.string()
      .email('Invalid email address')
      .required('Email is required'),
    age: Yup.number()
      .min(18, 'You must be at least 18 years old')
      .required('Age is required'),
  });

  const MyForm = () => (
    <Formik
      initialValues={{ name: '', email: '', age: '' }}
      validationSchema={validationSchema}
      onSubmit={(values) => {
        console.log('Form data:', values);
      }}
    >
      {({ isSubmitting }) => (
        <Form className="my-form">
          <div className="input-group">
            <label htmlFor="name">Name:</label>
            <Field name="name" type="text" />
            <ErrorMessage name="name" component="div" />
          </div>
          <div className="input-group">
            <label htmlFor="email">Email:</label>
            <Field name="email" type="email" />
            <ErrorMessage name="email" component="div" />
          </div>
          <div className="input-group">
            <label htmlFor="age">Age:</label>
            <Field name="age" type="number" />
            <ErrorMessage name="age" component="div" />
          </div>
          <button type="submit" disabled={isSubmitting}>Submit</button>
        </Form>
      )}
    </Formik>
  );

  export default MyForm;

In this example, the Yup schema defines the validation rules for each field, ensuring that the user provides a name, a valid email address, and an age that is at least 18. Formik handles the form state and submission, and ErrorMessage components are used to display validation errors.

Here’s how the form will work:

Demo of using yup with Formik

Using Yup with React Hook Form

React Hook Form is another popular library for managing forms in React. It offers a simple API and integrates well with Yup for schema-based validation. Here’s how you can use Yup with React Hook Form:

  import React from 'react';
  import { useForm } from 'react-hook-form';
  import { yupResolver } from '@hookform/resolvers/yup';
  import * as Yup from 'yup';

  // Define Yup validation schema
  const validationSchema = Yup.object().shape({
    name: Yup.string()
      .required('Name is required'),
    email: Yup.string()
      .email('Invalid email address')
      .required('Email is required'),
    age: Yup.number()
      .typeError("Age must be a number")
      .min(18, 'You must be at least 18 years old')
      .required('Age is required'),
  });

  const MyForm = () => {
    const { register, handleSubmit, formState: { errors } } = useForm({
      resolver: yupResolver(validationSchema),
    });

    const onSubmit = (data) => {
      console.log('Form data:', data);
    };

    return (
      <form onSubmit={handleSubmit(onSubmit)} className="my-form">
        <div className="input-group">
          <label htmlFor="name">Name:</label>
          <input {...register('name')} type="text" />
          {errors.name && <div>{errors.name.message}</div>}
        </div>
        <div className="input-group">
          <label htmlFor="email">Email:</label>
          <input {...register('email')} type="email" />
          {errors.email && <div>{errors.email.message}</div>}
        </div>
        <div className="input-group">
          <label htmlFor="age">Age:</label>
          <input {...register('age')} type="number" />
          {errors.age && <div>{errors.age.message}</div>}
        </div>
        <button type="submit">Submit</button>
      </form>
    );
  };

  export default MyForm;

In this example, the Yup validation schema is passed to the useForm hook via the yupResolver, which integrates Yup with React Hook Form. The register function is used to connect form fields to the validation schema, and validation errors are accessed through the errors object. If validation fails, error messages are displayed below the relevant fields.

Here’s how the form will work:

Demo of using yup with React Hook Form

Both Formik and React Hook Form make it easy to integrate Yup, allowing you to handle complex validation scenarios while keeping your code concise and maintainable. Whether you prefer Formik’s declarative approach or React Hook Form’s simplicity, Yup enhances your ability to build robust, user-friendly forms in React applications.

Beyond React: Using Yup for Non-React Forms

Yup’s versatility extends beyond React, making it an excellent choice for form validation in non-React applications as well. Whether you’re working with frontend frameworks like Vue.js or Angular, Yup offers a solution for managing form validation logic in a consistent and maintainable way.

Although the exact implementation of Yup will vary depending on the framework, the core principle remains the same: defining a Yup schema and using it to validate form data before processing.

For instance, in Vue.js, you might use Yup directly or in combination with the vuelidate or vee-validate libraries to perform validation in a declarative and reactive manner. In Angular, you can integrate Yup with reactive forms by incorporating it into your form control logic.

Here’s a brief example of using Yup in a Vue.js component directly:

  <template>

    <form class="my-form" @submit.prevent="onSubmit">
      <div :class="['input-group', !!errors.email && 'has-error']">
        <label class="form-label" for="email">{{ "Email : " }}</label>
        <input id="email" name="email" type="email" v-model="values.email"
          class="form-input" @blur="validate('email')" 
          @keypress="validate('email')" />
        <div class="form-input-hint" v-if="!!errors.email">
          {{ errors.email }}
        </div>
      </div>

      <div :class="['input-group', !!errors.name && 'has-error']">
        <label class="form-label" for="name">{{ "Name : " }}</label>
        <input id="name" name="name" type="text" v-model="values.name"
          class="form-input" @blur="validate('name')" 
          @keyup="validate('name')" />
        <div class="form-input-hint" v-if="!!errors.name">
          {{ errors.name }}
        </div>
      </div>

      <div :class="['input-group', !!errors.age && 'has-error']">
        <label class="form-label" for="name">{{ "Age : " }}</label>
        <input id="age" name="age" type="number" v-model="values.age"
          class="form-input" @blur="validate('age')" 
          @keyup="validate('age')" />
        <div class="form-input-hint" v-if="!!errors.age">
          {{ errors.age }}
        </div>
      </div>

      <button type="submit">Submit</button>
    </form>
  </template>

  <script>
  import * as Yup from 'yup';

  const formSchema = Yup.object().shape({
    name: Yup.string().required('Name is required'),
    email: Yup.string()
      .email('Invalid email address')
      .required('Email is required'),
    age: Yup.number()
      .typeError('Age must be a number')
      .required('Age is required')
      .min(18, 'You must be at least 18 years old'),
  })

  export default {
    data() {
      return {
        values: {
          name: '',
          email: '',
          age: null
        },
        errors: {
          name: '',
          email: '',
          age: null
        }
      };
    },
    methods: {
      onSubmit() {
        formSchema.validate(this.values, { abortEarly: false })
          .then(() => {
            console.log('submitted')
          })
          .catch(err => {
            if (err.inner)
            err.inner.forEach(error => {
              this.errors = { ...this.errors, [error.path]: error.message };
            });
          });
      },
      validate(field) {
        console.log(this.values)
        formSchema
          .validateAt(field, this.values)
          .then(() => {
            this.errors[field] = "";
          })
          .catch(err => {
            this.errors[err.path] = err.message;
          });
      }
    },
  };

  </script>

In this example, Yup is first used to define validation rules by using the shape method. Each field is set to be validated when the user enters a character and finally when they focus away from it. Upon submission, all the fields are validated and validation errors are managed reactively through Vue’s state management.

Here’s how this form will work:

Demo of using yup with Vue

By using Yup’s simplicity and reusability, you can conveniently apply the same validation logic across different platforms and frameworks, ensuring consistency and reducing the likelihood of errors. This cross-framework capability makes Yup a useful tool for developers working in diverse environments.

Advanced Features of Yup

Yup extends beyond basic validation, offering some advanced features that allow you to handle complex validation scenarios easily. These features can help you to create complex validation logic, perform asynchronous checks, and even transform data during validation.

Chained Validations

One of Yup’s most powerful features is its ability to chain multiple validations on a single field. This allows you to enforce several rules in a specific order, ensuring that each condition is met before the next one is evaluated. For example, you might require a string field to be both non-empty and match a specific regular expression pattern:

  const schema = Yup.object().shape({
    username: Yup.string()
      .required('Username is required')
      .min(4, 'Username must be at least 4 characters')
      .matches(/^[a-zA-Z0-9_]+$/, 
        'Username can only contain letters, numbers, and underscores'),
  });

In this example, the username field is required, must be at least 4 characters long, and can only contain letters, numbers, and underscores. If any of these conditions fail, the corresponding error message is displayed.

Conditional Validation

Yup also supports conditional validation, where validation rules can change based on the values of other fields. This is particularly useful in forms where certain fields are only required under specific conditions, such as when another checkbox is checked or a dropdown has a particular selection:

  const schema = Yup.object().shape({
    hasDiscount: Yup.boolean(),
    discountCode: Yup.string()
      .when('hasDiscount', {
        is: true,
        then: Yup.string().required('Discount code is required'),
        otherwise: Yup.string().notRequired(),
      }),
  });

Here, the discountCode field is only required if the hasDiscount field is true. If hasDiscount is false, the discountCode field is not required. This conditional logic makes your validation schema more dynamic and responsive to user input.

Asynchronous Validation

In some cases, validation may require server-side checks or other asynchronous operations. Yup supports asynchronous validation, allowing you to perform complex validations that involve API calls or database lookups. For instance, you might need to verify that a username is not already taken:

  const schema = Yup.object().shape({
    username: Yup.string()
      .required('Username is required')
      .test('checkUsername', 
        'Username is already taken', 
        async (value) => {
          const isAvailable = await checkUsernameAvailability(value);
          return isAvailable;
        }
      ),
  });

In this example, the test method is used to create a custom validation function that runs asynchronously. The function checks the availability of the username by calling an API and returns true or false based on the result. If the username is already taken, an error message is displayed.

Data Transformation and Sanitization

Yup isn’t just limited to validation; it can also be used to transform and sanitize data before it’s processed or saved. This feature is particularly useful when you need to standardize user input, such as trimming whitespace or converting text to lowercase:

  const schema = Yup.object().shape({
    email: Yup.string()
      .email('Invalid email address')
      .trim()
      .lowercase()
      .required('Email is required'),
  });

In this example, the email field is automatically trimmed of any leading or trailing whitespace and converted to lowercase before validation is applied. This ensures that the data is consistent and meets your application’s requirements.

Handling Validation Errors Gracefully

A critical aspect of form validation is how errors are communicated to the user. Yup provides various ways to handle validation errors gracefully, allowing you to provide clear and user-friendly feedback. By customizing error messages and leveraging Yup’s validation error objects, you can ensure that users are informed about what needs to be corrected and how:

  try {
    await schema.validate(data, { abortEarly: false });
  } catch (err) {
    const errors = err.inner.reduce((acc, error) => {
      acc[error.path] = error.message;
      return acc;
    }, {});
    console.log(errors);
  }

In this example, the validate method is configured to capture all validation errors by setting abortEarly to false. The errors are then processed and mapped to an object, which can be used to display error messages in a user-friendly format. This approach ensures that users receive comprehensive feedback, helping them correct all issues at once.

Best Practices and Considerations

One of the key principles to follow when implementing form validation with yup is the clear separation of concerns. By keeping validation logic separate from your UI components, you can ensure that your code remains modular and easier to manage. Implementing this can vary across frameworks as you’ve seen in the examples above. This separation not only simplifies the debugging process but also promotes the reusability of your validation schemas across different parts of your application.

Writing reusable validation schemas is another practice that can save time and effort. Common patterns, such as email validation, password strength checks, or phone number formats, can be encapsulated in reusable schemas that you can apply wherever needed. This approach reduces redundancy and ensures consistency in how data is validated throughout your application.

Performance optimization is also crucial, particularly when dealing with asynchronous validation, such as server-side checks. To minimize latency and improve user experience, consider strategies like debouncing validation triggers or batching multiple validations together. These techniques help avoid unnecessary validation calls and keep your application responsive.

Lastly, accessibility should always be a top priority. Ensure that validation errors are presented in a way that is accessible to all users, including those using screen readers or other assistive technologies. Clearly communicate the errors and provide guidance on how to correct them. Additionally, when testing your Yup validation logic, consider edge cases and different user inputs to ensure your validation is robust and handles a wide range of scenarios effectively.

Conclusion

Yup offers a powerful, flexible solution for handling form validation in both React and non-React applications. Through its schema validation approach, developers can ensure their forms collect accurate, high-quality data while maintaining clean and maintainable code. Whether you’re building complex validation logic or implementing simple checks, Yup provides the tools needed to create robust and reusable validation schemas that streamline development and improve user experience.

By integrating Yup into your projects, you can improve your form validation practices and ensure your applications remain reliable, efficient, and user-friendly.

What validation tool/framework do you use in your web apps? Let us know below!


Got Feedback?