Taming React forms with validations and masks using IMask, Yup, and React Hook Form.
Tags: react
Published: 2020-12-13 21:54:26

# TL;DR

The final code can be seen [at this codesandbox](https://codesandbox.io/s/taming-forms-vvfiq). The implementation is explained [here](#the-combination).

# Introduction

Creating forms with React was always a pain for me. I'm sure this happened because of some lack of technical knowledge. It could about HTML inputs and the data flow inside de `input` tag, how to handle validations and masks properly. But the biggest problem was that I had an unclear roles attribution in the component. By roles, I mean:

- Which part will handle the user input?
- Which methods will handle validations?
- If we need masks, should we keep them before calling the post method or validations?
- When do I have to apply masks? And unmask?

Imagine a React component with validations, masks, form methods, API callings all together in a messy code. That was the way I used to create React forms. Because of this, I decided to study more about forms.

The biggest issue with forms was the lack of clarity of the roles of each form part. The focus of my study was trying to figure out the existing roles and how to decouple them. Once these attributions were clear, a combination with [IMask](https://imask.js.org/), [Yup](https://github.com/jquense/yup), and [React Hook Form](https://react-hook-form.com/) was used to take care of these roles.

In this article, we won't talk about the implementation details of the libraries used. The main goal is to present an example using them together. Before showing the code, let's define the roles I'm talking about. 

# Form Roles

A form is a tool that is used to gather information in a structured and convenient way. Thus, we have the first role:

[*1. User interaction. Input and output. UI/UX.*](#user-interaction)

To achieve a good UX, sometimes we have to manipulate the way we display data to users. For instance, adding punctuations and spaces when a user types their phone number. The displayed data may be different from the data we'll send to the form processor - the entity that will receive the final data, like a [POST endpoint](https://en.wikipedia.org/wiki/POST_(HTTP)). This technique is called [data masking](https://en.wikipedia.org/wiki/Data_masking) and is our second role:

[*2. Data masking.*](#data-masking)

Before data is being sent to the processor, some data must be validated. This is very correlated with user experience. When the user types some information, if we detect that it is invalid, we should display some notification as soon as possible. That is our third role:

[*3. Data validation.*](#data-validation)

Finally, our last role is about handling forms actions, states, and events. As the user interacts with the form, we sometimes need to know when the user starts typing, when leaving an input, if the form contains some error, if it is submitted or submitting...

[*4. Form handler.*](#form-handler)

In summary, our roles are:

- [*1. User interaction. Input and output. UI/UX*](#user-interaction)
- [*2. Data masking.*](#data-masking)
- [*3. Data validation.*](#data-validation)
- [*4. Form handler.*](#form-handler)

# The Combination

We're going to create a form with the fields: email, date, [CPF](https://en.wikipedia.org/wiki/CPF_number) or [CNPJ](https://en.wikipedia.org/wiki/CNPJ), phone number, and currency. Each one of them has its validations and masks.

[A form with this implementation was created](https://codesandbox.io/s/taming-forms-vvfiq). 

## User Interaction

[Implementation can be seen here.](https://codesandbox.io/s/taming-forms-vvfiq?file=/src/App.tsx)

HTML and CSS (coded in the React way) are the ones that take care of this part. Some questions that drive this role are:

- How can we provide an easy and intuitive interface?
- How is the best way to present data to users?
- How to create a seamless way to user input data?

## Data Masking

[Implementation can be seen here.](https://codesandbox.io/s/taming-forms-vvfiq?file=/src/masks.ts)

This role takes care of data manipulation, in our case, masking. A helper that has some manipulation methods was created, I called it `masker`. It uses [IMask](https://imask.js.org/) under the hood to perform masking and unmasking.

```ts
/**
 * https://stackoverflow.com/a/10452789/8786986
 * @param args
 */
const masker = ({
  masked,
  transform,
  maskDefault
}: {
  masked: any;
  transform?: any;
  maskDefault?: any;
}) =>
  (function () {
    const mask = IMask.createPipe(
      masked,
      IMask.PIPE_TYPE.UNMASKED,
      IMask.PIPE_TYPE.MASKED
    );

    const unmask = IMask.createPipe(
      masked,
      IMask.PIPE_TYPE.MASKED,
      IMask.PIPE_TYPE.UNMASKED
    );

    const onChange = (e: any) => {
      const unmasked = unmask(e.target.value);
      const newValue = mask(unmasked);
      e.target.value = newValue;
    };

    return {
      mask,
      onChange,
      transform: transform || unmask,
      unmask,
      maskDefault: maskDefault || mask
    };
  })();
```

The first exposed method is the `onChange`. We use it together with `<input />` to handle the HTML input `onChange` event. This method takes the data typed by the user, applies the mask, and sets the masked back to `<input />`.

```tsx
<Input
  id="cpfOrCnpj"
  name="cpfOrCnpj"
  onChange={masks.cpfOrCnpjMask.onChange}
/>
```

The second method is the `transform` and it's used with [validations](#data-validation). This method takes the masked value and transforms it into the format that we'll send to the form processor.

The third one is `maskDefault`. This method takes initial values sent by the form processor and masks them. Once transformed, the user we'll see the initial data with masking. It's used with [form handler](#form-handler).

## Data Validation

[Implementation can be seen here.](https://codesandbox.io/s/taming-forms-vvfiq?file=/src/schema.ts)

[Yup](https://github.com/jquense/yup) is the one that manages this role. We create a schema that performs all data validations at the [validation phase](https://react-hook-form.com/get-started#SchemaValidation).

```ts
export const schema = yup.object().shape({
  email: yup.string().email().required(),
  date: yup
    .string()
    .transform(masks.dateMask.transform)
    .notRequired()
    .test("validateDate", "Invalid date", (value) => {
      return dateFns.isValid(dateFns.parse(value, "yyyy-MM-dd", new Date()));
    }),
  ...
}
```

Note how `masker.transform` was used. When validations are triggered, the data enters the Yup pipe with the mask. Before the validations start, we transform from masked to form processor format. For instance, if a phone number enters equals to `+55 16 91234-1234`, it's transformed to `16912341234`.

## Form Handler

[Implementation can be seen here.](https://codesandbox.io/s/taming-forms-vvfiq?file=/src/useForm.ts)

The chosen form handler was [React Hook Form](https://react-hook-form.com/). It connects with the `<input />` by the [`register`](https://react-hook-form.com/api#register) method.

It uses `masker.maskDefault` property to apply the mask to initial values that will be passed to [React Hook Form `useForm` `defaultValues`](https://react-hook-form.com/api#useForm) property. `getData` in the code below represents the method that returns the initial value from form processor.

```ts
const getDefaultValues = () => {
  const data = getData();
  return {
    ...data,
    date: masks.dateMask.maskDefault(data.date),
    cpfOrCnpj: masks.cpfOrCnpjMask.maskDefault(data.cpfOrCnpj),
    phone: masks.phoneMask.maskDefault(data.phone),
    currency: masks.currencyMask.maskDefault(data.currency)
  };
};
```

Finally, the last step of this role is the submit. When the user submits and all data is valid, it handles the submission. `postData` represents the method that will send the data to the form processor. Also, it's important to remember that the data from [`handleSubmit`](https://react-hook-form.com/api#handleSubmit) has the form processor format because the `transform` called at the beginning of the [Yup schema](#data-validation).

```ts
const { handleSubmit, reset } = form;
const onSubmit = handleSubmit((data) => postData(data));
```

# Conclusion

In my opinion, these libraries created a great fit together. This combination wasn't widely tested for me, but I'm almost sure that it'll be suitable for many form implementations.

I'm open to discussions and I want to hear your feedback. You can comment here or reach me on [Twitter](https://twitter.com/arantespp), my DMs are open.

I hope you enjoy this text as I did writing it. I really hope this article helps you 😁

--- 

<span>Photo by <a href="https://unsplash.com/@cytonn_photography?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Cytonn Photography</a> on <a href="https://unsplash.com/s/photos/document?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>