• 18-19 College Green, Dublin 2
  • 01 685 9088
  • info@cunninghamwebsolutions.com
  • cunninghamwebsolutions
    Cunningham Web Solutions
    • Home
    • About Us
    • Our Services
      • Web Design
      • Digital Marketing
      • SEO Services
      • E-commerce Websites
      • Website Redevelopment
      • Social Media Services
    • Digital Marketing
      • Adwords
      • Social Media Services
      • Email Marketing
      • Display Advertising
      • Remarketing
    • Portfolio
    • FAQ’s
    • Blog
    • Contact Us
    MENU CLOSE back  

    Creating Your Own React Validation Library: The Basics (Part 1)

    You are here:
    1. Home
    2. Web Design
    3. Creating Your Own React Validation Library: The Basics (Part 1)
    Thumbnail for 23000
    Smashing Editorial

    Creating Your Own React Validation Library: The Basics (Part 1)

    Creating Your Own React Validation Library: The Basics (Part 1)

    Kristofer Selbekk

    2019-05-16T13:00:59+02:00
    2019-05-16T12:35:10+00:00

    I’ve always thought form validation libraries were pretty cool. I know, it’s a niche interest to have — but we use them so much! At least in my job — most of what I do is constructing more or less complex forms with validation rules that depend on earlier choices and paths. Understanding how a form validation library would work is paramount.

    Last year, I wrote one such form validation library. I named it “Calidation”, and you can read the introductory blog post here. It’s a good library that offers a lot of flexibility and uses a slightly different approach than the other ones on the market. There are tons of other great libraries out there too, though — mine just worked well for our requirements.

    Today, I’m going to show you how to write your very own validation library for React. We will go through the process step by step, and you’ll find CodeSandbox examples as we go along. By the end of this article, you will know how to write your own validation library, or at the very least have a deeper understanding of how other libraries implement “the magic of validation”.

    • Part 1: The Basics
    • Part 2: The Features
    • Part 3: The Experience

    Step 1: Designing The API

    The first step of creating any library is designing how it’s going to be used. It lays the foundation for a lot of the work to come, and in my opinion, it’s the single most important decision you’re going to make in your library.

    It’s important to create an API that’s “easy to use”, and yet flexible enough to allow for future improvements and advanced use cases. We’ll try to hit both of these goals.

    We’re going to create a custom hook that will accept a single configuration object. This will allow for future options to be passed without introducing breaking changes.

    A Note On Hooks

    Hooks is a pretty new way of writing React. If you’ve written React in the past, you might not recognize a few of these concepts. In that case, please have a look at the official documentation. It’s incredibly well written, and takes you through the basics you need to know.

    We’re going to call our custom hook useValidation for now. Its usage might look something like this:

    const config = {
      fields: {
        username: {
          isRequired: { message: 'Please fill out a username' },
        },
        password: {
          isRequired: { message: 'Please fill out a password' },
          isMinLength: { value: 6, message: 'Please make it more secure' }
        }
      },
      onSubmit: e => { /* handle submit */ }
    };
    const { getFieldProps, getFormProps, errors } = useValidation(config);
    

    The config object accepts a fields prop, which sets up the validation rules for each field. In addition, it accepts a callback for when the form submits.

    The fields object contains a key for each field we want to validate. Each field has its own config, where each key is a validator name, and each value is a configuration property for that validator. Another way of writing the same would be:

    {
      fields: {
        fieldName: {
          oneValidator: { validatorRule: 'validator value' },
          anotherValidator: { errorMessage: 'something is not as it should' }
        }
      }
    }
    

    Our useValidation hook will return an object with a few properties — getFieldProps, getFormProps and errors. The two first functions are what Kent C. Dodds calls “prop getters” (see here for a great article on those), and is used to get the relevant props for a given form field or form tag. The errors prop is an object with any error messages, keyed per field.

    This usage would look like this:

    const config = { ... }; // like above
    const LoginForm = props => {
      const { getFieldProps, getFormProps, errors } = useValidation(config);
      return (
        <form {...getFormProps()}>
          <label>
            Username<br/>
            <input {...getFieldProps('username')} />
            {errors.username && <div className="error">{errors.username}</div>}
          </label>
          <label>
            Password<br/>
            <input {...getFieldProps('password')} />
            {errors.password && <div className="error">{errors.password}</div>}
          </label>
          <button type="submit">Submit my form</button>
        </form>
      );
    };
    

    Alrighty! So we’ve nailed the API.

    • See CodeSandbox demo

    Note that we’ve created a mock implementation of the useValidation hook as well. For now, it’s just returning an object with the objects and functions we require to be there, so we don’t break our sample implementation.

    Storing The Form State 💾

    The first thing we need to do is storing all of the form state in our custom hook. We need to remember the values of each field, any error messages and whether or not the form has been submitted. We’ll use the useReducer hook for this since it allows for the most flexibility (and less boilerplate). If you’ve ever used Redux, you’ll see some familiar concepts — and if not, we’ll explain as we go along! We’ll start off by writing a reducer, which is passed to the useReducer hook:

    const initialState = {
      values: {},
      errors: {},
      submitted: false,
    };
    
    function validationReducer(state, action) {
      switch(action.type) {
        case 'change': 
          const values = { ...state.values, ...action.payload };
          return { 
            ...state, 
            values,
          };
        case 'submit': 
          return { ...state, submitted: true };
        default: 
          throw new Error('Unknown action type');
      }
    }
    

    What’s A Reducer? 🤔

    A reducer is a function that accepts an object of values and an “action” and returns an augmented version of the values object.

    Actions are plain JavaScript objects with a type property. We’re using a switch statement to handle each possible action type.

    The “object of values” is often referred to as state, and in our case, it’s the state of our validation logic.

    Our state consists of three pieces of data — values (the current values of our form fields), errors (the current set of error messages) and a flag isSubmitted indicating whether or not our form has been submitted at least once.

    In order to store our form state, we need to implement a few parts of our useValidation hook. When we call our getFieldProps method, we need to return an object with the value of that field, a change-handler for when it changes, and a name prop to track which field is which.

    function validationReducer(state, action) {
      // Like above
    }
    
    const initialState = { /* like above */ };
    
    const useValidation = config => {
      const [state, dispatch] = useReducer(validationReducer, initialState);
      
      return {
        errors: state.errors,
        getFormProps: e => {},
        getFieldProps: fieldName => ({
          onChange: e => {
            if (!config.fields[fieldName]) {
              return;
            }
            dispatch({ 
              type: 'change', 
              payload: { [fieldName]: e.target.value } 
            });
          },
          name: fieldName,
          value: state.values[fieldName],
        }),
      };
    };
    

    The getFieldProps method now returns the props required for each field. When a change event is fired, we ensure that field is in our validation configuration, and then tell our reducer a change action took place. The reducer will handle the changes to the validation state.

    • See CodeSandbox demo

    Validating Our Form 📄

    Our form validation library is looking good, but isn’t doing much in terms of validating our form values! Let’s fix that. 💪

    We’re going to validate all fields on every change event. This might not sound very efficient, but in the real world applications I’ve come across, it isn’t really an issue.

    Note, we’re not saying you have to show every error on every change. We’ll revisit how to show errors only when you submit or navigates away from a field, later in this article.

    How To Pick Validator Functions

    When it comes to validators, there are tons of libraries out there that implement all the validation methods you’d ever need. You can also write your own if you want. It’s a fun exercise!

    For this project, we’re going to use a set of validators I wrote some time ago — calidators. These validators have the following API:

    function isRequired(config) {
      return function(value) {
        if (value === '') {
          return config.message;
        } else {
          return null;
        }
      };
    }
    
    // or the same, but terser
    
    const isRequired = config => value => 
        value === '' ? config.message : null;
    

    In other words, each validator accepts a configuration object and returns a fully-configured validator. When that function is called with a value, it returns the message prop if the value is invalid, or null if it’s valid. You can look at how some of these validators are implemented by looking at the source code.

    To access these validators, install the calidators package with npm install calidators.

    Validate a single field

    Remember the config we pass to our useValidation object? It looks like this:

    { 
      fields: {
        username: {
          isRequired: { message: 'Please fill out a username' },
        },
        password: {
          isRequired: { message: 'Please fill out a password' },
          isMinLength: { value: 6, message: 'Please make it more secure' }
        }
      },
      // more stuff
    }
    

    To simplify our implementation, let’s assume we only have a single field to validate. We’ll go through each key of the field’s configuration object, and run the validators one by one until we either find an error or are done validating.

    import * as validators from 'calidators';
    
    function validateField(fieldValue = '', fieldConfig) {
      for (let validatorName in fieldConfig) {
        const validatorConfig = fieldConfig[validatorName];
        const validator = validators[validatorName];
        const configuredValidator = validator(validatorConfig);
        const errorMessage = configuredValidator(fieldValue);
    
        if (errorMessage) {
          return errorMessage;
        }
      }
      return null;
    }
    

    Here, we’ve written a function validateField, which accepts the value to validate and the validator configs for that field. We loop through all of the validators, pass them the config for that validator, and run it. If we get an error message, we skip the rest of the validators and return. If not, we try the next validator.

    Note: On validator APIs

    If you choose different validators with different APIs (like the very popular validator.js), this part of your code might look a bit different. For brevity’s sake, however, we let that part be an exercise left to the reader.

    Note: On for…in loops

    Never used for...in loops before? That’s fine, this was my first time too! Basically, it iterates over the keys in an object. You can read more about them at MDN.

    Validate all the fields

    Now that we’ve validated one field, we should be able to validate all fields without too much trouble.

    function validateField(fieldValue = '', fieldConfig) {
      // as before
    }
    
    function validateFields(fieldValues, fieldConfigs) {
      const errors = {};
      for (let fieldName in fieldConfigs) {
        const fieldConfig = fieldConfigs[fieldName];
        const fieldValue = fieldValues[fieldName];
    
        errors[fieldName] = validateField(fieldValue, fieldConfig);
      }
      return errors;
    }
    

    We’ve written a function validateFields that accepts all field values and the entire field config. We loop through each field name in the config and validate that field with its config object and value.

    Next: Tell our reducer

    Alrighty, so now we have this function that validates all of our stuff. Let’s pull it into the rest of our code!

    First, we’re going to add a validate action handler to our validationReducer.

    function validationReducer(state, action) {
      switch (action.type) {
        case 'change':
          // as before
        case 'submit':
          // as before
        case 'validate': 
          return { ...state, errors: action.payload };
        default:
          throw new Error('Unknown action type');
      }
    }
    

    Whenever we trigger the validate action, we replace the errors in our state with whatever was passed alongside the action.

    Next up, we’re going to trigger our validation logic from a useEffect hook:

    const useValidation = config => {
      const [state, dispatch] = useReducer(validationReducer, initialState);
    
      useEffect(() => {
        const errors = validateFields(state.fields, config.fields);
        dispatch({ type: 'validate', payload: errors });
      }, [state.fields, config.fields]);
      
      return {
        // as before
      };
    };
    

    This useEffect hook runs whenever either our state.fields or config.fields changes, in addition to on first mount.

    Beware Of Bug 🐛

    There’s a super subtle bug in the code above. We’ve specified that our useEffect hook should only re-run whenever the state.fields or config.fields change. Turns out, “change” doesn’t necessarily mean a change in value! useEffect uses Object.is to ensure equality between objects, which in turn uses reference equality. That is — if you pass a new object with the same content, it won’t be the same (since the object itself is new).

    The state.fields are returned from useReducer, which guarantees us this reference equality, but our config is specified inline in our function component. That means the object is re-created on every render, which in turn will trigger the useEffect above!

    To solve this, we need to use for the use-deep-compare-effect library by Kent C. Dodds. You install it with npm install use-deep-compare-effect, and replace your useEffect call with this instead. This makes sure we do a deep equality check instead of a reference equality check.

    Your code will now look like this:

    import useDeepCompareEffect from 'use-deep-compare-effect';
    
    const useValidation = config => {
      const [state, dispatch] = useReducer(validationReducer, initialState);
    
      useDeepCompareEffect(() => {
        const errors = validateFields(state.fields, config.fields);
        dispatch({ type: 'validate', payload: errors });
      }, [state.fields, config.fields]);
      
      return {
        // as before
      };
    };
    

    A Note On useEffect

    Turns out, useEffect is a pretty interesting function. Dan Abramov wrote a really nice, long article on the intricacies of useEffect if you’re interested in learning all there is about this hook.

    Now things are starting to look like a validation library!

    • See CodeSandbox demo

    Handling Form Submission

    The final piece of our basic form validation library is handling what happens when we submit the form. Right now, it reloads the page, and nothing happens. That’s not optimal. We want to prevent the default browser behavior when it comes to forms, and handle it ourselves instead. We place this logic inside the getFormProps prop getter function:

    const useValidation = config => {
      const [state, dispatch] = useReducer(validationReducer, initialState);
      // as before
      return {
        getFormProps: () => ({
          onSubmit: e => {
            e.preventDefault();
            dispatch({ type: 'submit' });
            if (config.onSubmit) {
              config.onSubmit(state);
            }
          },
        }),
        // as before
      };
    };
    

    We change our getFormProps function to return an onSubmit function, that is triggered whenever the submit DOM event is triggered. We prevent the default browser behavior, dispatch an action to tell our reducer we submitted, and call the provided onSubmit callback with the entire state — if it’s provided.

    Summary

    We’re there! We’ve created a simple, usable and pretty cool validation library. There’s still tons of work to do before we can dominate the interwebs, though.

    Stay tuned for Part 2 next week!

    (dm, il)

    From our sponsors: Creating Your Own React Validation Library: The Basics (Part 1)

    Posted on 16th May 2019Web Design
    FacebookshareTwittertweetGoogle+share

    Related posts

    Archived
    22nd March 2023
    Archived
    18th March 2023
    Archived
    20th January 2023
    Thumbnail for 25788
    Handling Continuous Integration And Delivery With GitHub Actions
    19th October 2020
    Thumbnail for 25778
    A Monthly Update With New Guides And Community Resources
    19th October 2020
    Thumbnail for 25781
    Supercharge Testing React Applications With Wallaby.js
    19th October 2020
    Latest News
    • Archived
      22nd March 2023
    • Archived
      18th March 2023
    • Archived
      20th January 2023
    • 20201019 ML Brief
      19th October 2020
    • Thumbnail for 25788
      Handling Continuous Integration And Delivery With GitHub Actions
      19th October 2020
    • Thumbnail for 25786
      The Future of CX with Larry Ellison
      19th October 2020
    News Categories
    • Digital Marketing
    • Web Design

    Our services

    Website Design
    Website Design

    A website is an important part of any business. Professional website development is an essential element of a successful online business.

    We provide website design services for every type of website imaginable. We supply brochure websites, E-commerce websites, bespoke website design, custom website development and a range of website applications. We love developing websites, come and talk to us about your project and we will tailor make a solution to match your requirements.

    You can contact us by phone, email or send us a request through our online form and we can give you a call back.

    More Information

    Digital Marketing
    Digital Marketing

    Our digital marketeers have years of experience in developing and excuting digital marketing strategies. We can help you promote your business online with the most effective methods to achieve the greatest return for your marketing budget. We offer a full service with includes the following:

    1. Social Media Marketing

    2. Email & Newsletter Advertising

    3. PPC - Pay Per Click

    4. A range of other methods are available

    More Information

    SEO
    SEO Services

    SEO is an essential part of owning an online property. The higher up the search engines that your website appears, the more visitors you will have and therefore the greater the potential for more business and increased profits.

    We offer a range of SEO services and packages. Our packages are very popular due to the expanse of on-page and off-page SEO services that they cover. Contact us to discuss your website and the SEO services that would best suit to increase your websites ranking.

    More Information

    E-commerce
    E-commerce Websites

    E-commerce is a rapidly growing area with sales online increasing year on year. A professional E-commerce store online is essential to increase sales and is a reflection of your business to potential customers. We provide professional E-commerce websites custom built to meet our clients requirements.

    Starting to sell online can be a daunting task and we are here to make that journey as smooth as possible. When you work with Cunningham Web Solutions on your E-commerce website, you will benefit from the experience of our team and every detail from the website design to stock management is carefully planned and designed with you in mind.

    More Information

    Social Media Services
    Social Media Services

    Social Media is becoming an increasingly effective method of marketing online. The opportunities that social media marketing can offer are endless and when managed correctly can bring great benefits to every business.

    Social Media Marketing is a low cost form of advertising that continues to bring a very good ROI for our clients. In conjuction with excellent website development and SEO, social media marketing should be an essential part of every digital marketing strategy.

    We offer Social Media Management packages and we also offer Social Media Training to individuals and to companies. Contact us to find out more.

    More Information

    Cunningham Web Solutions
    © Copyright 2025 | Cunningham Web Solutions
    • Home
    • Our Services
    • FAQ's
    • Account Services
    • Privacy Policy
    • Contact Us