0.1.1 • Published 4 years ago

react-use-form-state-validator v0.1.1

Weekly downloads
1
License
MIT
Repository
github
Last release
4 years ago

react-use-form-state-validator

react-use-form-state-validator is a plugin for react-use-form-state (written by @wsdm). react-use-form-state is an elegant React Hook that simplifies managing form state. It supports HTML5 form validation out-of-the-box and returns the default HTML5 validation messages as error messages.

For more complex validation react-use-form-state exposes a validate hook in it's field config object. You would have to write your own validation logic and compose your own error messages that suit the situation. This plugin's aim is to simplify that process. It leverages the popular validator package to handle various validation types and provides an easy way to manage error messages.

Installation

Use npm or yarn to add the package to your project:

npm install react-use-form-state-validator --save
yarn add react-use-form-state-validator

After that you can import it's utility functions as named imports, for example:

import { validate, isRequired } from 'react-use-form-state-validator';

Usage

The most common scenario would leverage the following three utilities that react-use-form-state-validator exposes:

  • setErrorMessages method
  • validate method
  • validator methods

Let's look at a basic example before we dive in.

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { setErrorMessages } from 'react-use-form-state-validator';

import MyComponent from './MyComponent';

setErrorMessages({
    isRequired: '{{ capitalize(fieldLabel) }} is a required field',
    isEmail: 'This is not a valid email address',
    isLength: 'Please enter a minimum of {{min}} characters',
});

ReactDOM.render(
    <React.StrictMode>
        <MyComponent />
    </React.StrictMode>,
    document.getElementById('root')
);
// MyComponent.js
import React from "react";
import { useFormState } from 'react-use-form-state';
import { validate, isRequired, isEmail, isLength } from 'react-use-form-state-validator';

const MyComponent = () => {
    const [formState, { text, password }] = useFormState();

    return (
        <div>
            <h1>Create Account</h1>
            <form>
                <p>
                    <label htmlFor="nameInput">Name</label>
                    <input
                        id="nameInput"
                        {...text({
                            name: 'name',
                            validate: validate(
                                'Name',
                                isRequired(),
                            ),
                        })}
                    />
                </p>
                <p>
                    <label htmlFor="emailInput">Email address</label>
                    <input
                        id="emailInput"
                        {...text({
                            name: 'email',
                            validate: validate(
                                'Email Address',
                                isRequired(),
                                isEmail(),
                            ),
                        })}
                    />
                </p>
                <p>
                    <label htmlFor="passwordInput">Password</label>
                    <input
                        id="passwordInput"
                        {...password({
                            name: 'pwd',
                            validate: validate(
                                'Password',
                                isRequired(),
                                isLength([{ min: 6 }]),
                            ),
                        })}
                    />
                </p>
            </form>
        </div>
    );
};

export default MyComponent;

As you can see we use setErrorMessages() during initialization and validate() and validator methods inside our component. The implementation of these ingredients will result in an errors object (as part of react-use-form-state's form state object) that lists all errors and messages that are applicable, grouped by field, something like:

"errors": {
    "name": [
        {
            "validatorName": "isRequired",
            "error": "Name is a required field"
        }
    ],
    "email": [
        {
            "validatorName": "isEmail",
            "error": "This is not a valid email address"
        }
    ]
}

setErrorMessages()

This method allows you to provide error message content for any errors that may occur. You would typically set this once, during the initialization of your app. The only argument you pass in should be an object with key/value pairs, where the key matches one of the validators (see validate() and validator methods) and the value would be the error message itself. Within the error message you can use curly brackets for dynamic values. These dynamic values can be passed in through the validate method or through a validator method (once again see validate() and validator methods).

import { setErrorMessages } from 'react-use-form-state-validator';

setErrorMessages({
    isRequired: 'Please enter a valid value',
    isEmail: 'Please enter a valid email address',
    isLength: 'Please enter a minimum of {{min}} and a maximum of {{max}} character',
    'isLength:min': '{{ capitalize(fieldLabel) }} should contain at least {{min}} characters',
});

A couple of things are happening here. First we see two examples of the most basic version of setting an error message: isRequired and isEmail are both given a plain error message. This means that whenever you use isRequired or isEmail for validation and an error occurs, these are the messages that will be used (unless explicitly overwritten, see validate() and validator methods).

For isLength we see the use of dynamic values through curly brackets. The error message will be interpolated and the {{min}} and {{max}} placeholders will be replaced by actual values. These values are supplied through either the validate method or the validator itself.

Then there is isLength:min. The key is separated by a colon into a validator and a modifier. Sometimes it's not enough to provide just one error message for a certain validator. For example, for isLength, you probably want to provide different messages for when there's both a minimum and a maximum and for when there is only a minimum. That's where modifiers come in. The error message for isLength:min is used whenever you use the isLength validator and you provide it the min modifier through the _modifier property in the error messages object.

<input
   id="nameInput"
   {...text({
       name: 'name',
       validate: validate(
           isLength([{ min: 3 }], { _modifier: 'min' })
       ),
   })}
/>

Another thing you may notice in the error message above for isLength:min, is the use of capitalize(). This is called a template helper and can be used inside the curly brackets. These are the built-in template helpers:

  • capitalize: uppercases the first character of the value
  • uncapitalize: lowercases the first character of the value
  • uppercase: uppercases the entire value
  • lowercase: lowercases the entire value

You can also add your own template helper via addTemplateHelper:

import { addTemplateHelper, setErrorMessages } from 'react-use-form-state-validator';

addTemplateHelper('charCount', str => str.length.toString());

setErrorMessages({
    'isLength:min': '{{fieldLabel}} should contain at least {{min}} characters (you entered {{ charCount(fieldValue) }} characters)',
});

validate() and validator methods

The validate method is what you pass into react-use-form-state's validate hook in the field config object. It receives information about the current form values and the event that triggered it to run. Read more about react-use-form-state's validate hook here.

The first parameter for validate() is special in the sense that is serves multiple purposes based on the type you pass in.

You can pass in a context object with the following properties (all optional):

  • fieldLabel: string, available for interpolation
  • errorMessages: key/value object, will get merged with the global error messages set via setErrorMessages, but only for the current validation
  • errorMessageVars: key/value object, variables will be available for interpolation, only for the current validation

You can pass in a string, which will treated as the fieldLabel. It's basically a shorthand for passing in { fieldLabel: 'My field label' }.

Or you can just start passing in validator methods for arguments.

Most use cases won't need any specific error messages or variables for a single validation, so the most common scenario would be passing in a string to set the field label for interpolation, followed by some validators:

validate(
    'Email address', // shorthand for { fieldLabel: 'Email address' }
    isRequired(),
    isEmail(),
)

The validators react-use-form-state-validator expose have the same names as the methods from the validator package. There's a chance this package does not contain all validators from the validator package. Mainly because I'm a fan of named exports and I will need to add new validators manually. If you miss something that's included in the validator package, shoot me a message or open a pull request.

In addition to the validators from the validator package, there is also isRequired which inverses isEmpty: it checks whether an input field has a value.

Validator signature

A validator signature looks like this:

validator(validatorArguments: array, errorMessageVariables: object, errorMessage: string)

In most cases you would only use validatorArguments to pass in any arguments that the validator needs (these are passed on to the validator package), in conjunction with the global use of setErrorMessages. But in some cases errorMessageVariables and errorMessage may come in handy.

The validatorArguments are passed on to the validator package to actually handle the validation. Have a look at the validators and params you can pass in here. Skip the first parameter (described as str), the input's value is passed in automatically. There's is type restriction per validator on this array should you use Typescript.

The errorMessageVariables (which should be a key: value object) are used to interpolate any dynamic values you may define in your error messages using double curly brackets. For example, say you have defined an error message as

Hey {{ userName }}, this is invalid

passing in { userName: 'Jacob' } would result in this interpolated error message

Hey Jacob, this is invalid

There are two values available for interpolation that you won't have to provide in the error message variables object yourself: fieldLabel and fieldValue.

  • The label will be taken from the context object (or its string shorthand) you pass into validate(). If no fieldLabel is defined within that context object, it will be given the name of the input field as value.
  • The current value of the field will be automagically provided (in a stringified form).

There is one special property you can also pass into this object: _modifier. Read about it in the setErrorMessages section.

Finally there is errorMessage. It's a way to explicitly set an error message for the current validation on the current field. Pass in a string (it may contain dynamic values in curly brackets).

Moving up Validator parameters

Now, because some validators of the validator package don't require any arguments and you would usually not use errorMessageVariables in conjunction with an explicit errorMessage, parameters in the Validator signature can be 'moved up'. Specifically because the types differ. So passing in an object as the first parameter would skip setting validator arguments alltogether and define error message variables. The error message itself (the string parameter) would then passed in as the second parameter. Passing in a string as the first parameter would skip setting both validator arguments as well as error message variables.

Custom Validator

Up until now we've seen built-in validators being passed to validate() in a composable manner. But sometimes the built-in validators don't quite cut it and you want to roll your own validation. You could of course pass your own validator into react-use-form-state's validate hook, but you will lose the benefit of combining built-in validators with your own.

That's where custom validators come in. They very much look like the built-in validators. These are the arguments:

  • validatorName: string, used for identifying error messages set via setErrorMessages()
  • validator: validator function, has the same signature (*) react-use-form-state's validate hook has, so receives the value, values and event (read more here)
  • errorMessageVariables: key/value object
  • explicitErrorMessage: string

(*) The value (first argument) your custom validator function receives is derived from the values object, containing all form values, instead of the value react-use-form-state emits. This is because the value that react-use-form-state emits is somewhat inconsistent depending on field type and event it was triggered by.

There is one key difference you should keep in mind. The react-use-form-state example here returns different error messages for different validations. Your custom validator however, should focus on a single validation and should return true (no error) or false (error). Error messages are derived from the globally set error messages, the current validation error messages or the explicitly set error message. You can pass in multiple custom validators to handle different custom validations (giving each one a different name to indentify the corresponding error messages).

import { validate, isRequired, customValidator } from 'react-use-form-state-validator';

validate(
    'Name',
    isRequired(),
    isAlpha(),
    customValidator(
        'isChuckNorris',
        (value, values, e) => value === 'Chuck Norris',
        'Looks like you\'re not Chuck Norris...'
    )
)

Gotchas

First validator argument

Notice that when the first validator argument in the validatorArguments array is an object, the keys of that object can also be used as error message variables. For quite a few validators the first argument is an options object (once again see validators here), so this is a common scenario and takes care of not having to define the variable twice (once in validator arguments and once in error message variables). In this case min with value 10 will be used during interpolation of the error message:

isLength([{ min: 10 }], 'Please enter a minimum of {{ min }} characters')

Checkboxes and multiselects

Checkboxes and multiselects only support the isRequired validator.

For a single checkbox without a value, react-use-form-state gives us true if checked, false if unchecked. So, validating it with isRequired() will yield an error message if the value is false.

Grouped checkboxes with the same name require values. For these checkboxes, as well as for multiselect fields react-use-form-states gives us an array with the selected values. Validating it with isRequired() will yield an error if no selection is made (ie. the value is an empty array).

Thanks

A big thanks to @wsdm for writing react-use-form-state in the first place! And a big thanks to @keesvanlierop as well, for reviewing my code and suggesting some improvements :)

License

MIT