Validating forms in React Apps with Zod
Published: 2023-11-30 23:06:23

After a lot of attempts, and experimenting different versions of it, I’ve come with what I'd say is the best way to validate forms. In this post I'll try to explain, step by step, how I do it. I hope this helps you in any way, as others helped me with their posts here.

So, let's go!

## Starting setup

Lets start with a simple form, with some of the most requested data.


![form draft](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/whgxidyhfysw42cew3xo.png)


```javascript
// component Form
export function Form() {
  return (
    <form className="form">
      <label htmlFor="">Name</label>
      <input type="text" />

      <label htmlFor="">Email</label>
      <input type="email" />

      <label htmlFor="">Password</label>
      <input type="Password" />

      <label htmlFor="">Number</label>
      <input type="number" />

			<button type="submit" className="button">
        Enviar
      </button>
    </form>
  );
}

// main page
import './App.css';
import { Form } from './components/Form';

function App() {
  return (
    <div className="container">
      <Form />
    </div>
  );
}

export default App;
```
### Installing React Hook Form
`$ npm install react-hook-form`

### Installing Zod
`$ npm install zod`

## Adapting Form component to React Hook Form

To use React Hook Form, we will adequate our form according to the basic example usage, from the documentation:

- import register, handleSubmit and errors from useForm
- register must be inserted on every input, with its respective name
- call handleSubmit, from React Hook Form, at the 'onSubmit’ method of the form, receiving the ‘submitForm’, created by us. The submitForm function is where you will decide how to submit your data. In this example, we will only console the form data.
- if the input is not validated at submit, an error will be presented at 'errors’, to the respective not validated input.

```javascript
import { useForm } from 'react-hook-form';

interface Inputs {
  name: string;
  email: string;
  password: string;
  number: number;
}

export function Form() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>();

  function submitForm() {
    console.log('submit');
  }

  return (
    <form className="form" onSubmit={handleSubmit(submitForm)}>
      <label htmlFor="">Name</label>
      <input type="text" {...register('name')} />

      {errors?.name && 'erro'}

      <label htmlFor="">Email</label>
      <input type="email" {...register('email', { required: true })} />
      {errors?.email && 'erro'}

      <label htmlFor="">Password</label>
      <input type="Password" {...register('password')} />

      <label htmlFor="">Number</label>
      <input type="number" {...register('number')} />

      <button type="submit" className="button">
        Enviar
      </button>
    </form>
  );
}
```

## Using Zod

Now, we will add Zod validation to the form. First, we need to install it, along with @hookform/resolvers, to correctly use Zod with React Hook Form:

### Installing Zod

```jsx
$ npm install zod
$ npm install @hookform/resolvers
```

### Creating the validation Schema

Now, we will create an object schema, and infer its type:

```jsx
import { z } from 'zod';
import { zodResolver } from "@hookform/resolvers/zod";

const validationSchema = z.object({
  name: z.string().min(1),
  email: z.string().min(1).email(),
  password: z.string().min(1),
  phone: z.string().min(1),
});

type SchemaProps = z.infer<typeof validationSchema>;
```

We also need to change our useForm, passing its resolver: 

```jsx
const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<SchemaProps>({
    resolver: zodResolver(validationSchema)
  });
```

Don't forget to put the error messages in the form!

```jsx
// form should look something like this
<form className="form" onSubmit={handleSubmit(submitForm)}>
      <div>
        <label htmlFor="">Name</label>
        {errors?.name && <span>{errors.name.message}</span>}
      </div>
      <input type="text" {...register('name')} />

      <div>
        <label htmlFor="">Email</label>
        {errors?.email && <span>{errors.email.message}</span>}
      </div>
      <input type="email" {...register('email')} />

      <div>
        <label htmlFor="">Password</label>
        {errors?.password && <span>{errors.password.message}</span>}
      </div>
      <input type="Password" {...register('password')} />

      <div>
        <label htmlFor="">Phone Number</label>
        {errors?.phone && <span>{errors.phone.message}</span>}
      </div>
      <input type="string" {...register('phone')} />

      <button type="submit" className="button">
        Enviar
      </button>
    </form>
```


The form, with active errors, should look like this:

![active errors form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qn3d87uvsk21mu9fnhez.png)

## Customizing

Now, let's customize our validation. We will:

- insert custom error messages to each validation, of each property
- insert strong password validation (min 8 characters, one uppercase, one lowercase, one number and one special character)
- insert phone number validation (brazilian phone number)

```jsx
// phone validation (Brazil)
const phoneValidation = new RegExp(
  /^(?:(?:\+|00)?(55)\s?)?(?:\(?([1-9][0-9])\)?\s?)?(?:((?:9\d|[2-9])\d{3})\-?(\d{4}))$/
);

// Minimum 8 characters, at least one uppercase letter, one lowercase letter, one number and one special character
const passwordValidation = new RegExp(
  /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/
);

const validationSchema = z.object({
  name: z.string().min(1, { message: 'Must have at least 1 character' }),
  email: z
    .string()
    .min(1, { message: 'Must have at least 1 character' })
    .email({
      message: 'Must be a valid email',
    }),
  password: z
    .string()
    .min(1, { message: 'Must have at least 1 character' })
    .regex(passwordValidation, {
      message: 'Your password is not valid',
    }),
  phone: z
    .string()
    .min(1, { message: 'Must have at least 1 character' })
    .regex(phoneValidation, { message: 'invalid phone' }),
});
```

For phone and password validations, we use the .regex, calling our validation, and passing the error message.

To sum up, this is the entire code of the component: 

```jsx
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

const phoneValidation = new RegExp(
  /^(?:(?:\+|00)?(55)\s?)?(?:\(?([1-9][0-9])\)?\s?)?(?:((?:9\d|[2-9])\d{3})\-?(\d{4}))$/
);

const passwordValidation = new RegExp(
  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
);

const validationSchema = z.object({
  name: z.string().min(1, { message: 'Must have at least 1 character' }),
  email: z
    .string()
    .min(1, { message: 'Must have at least 1 character' })
    .email({
      message: 'Must be a valid email',
    }),
  password: z
    .string()
    .min(1, { message: 'Must have at least 1 character' })
    .regex(passwordValidation, {
      message: 'Your password is not valid',
    }),
  phone: z
    .string()
    .min(1, { message: 'Must have at least 1 character' })
    .regex(phoneValidation, { message: 'invalid phone' }),
});

type SchemaProps = z.infer<typeof validationSchema>;

export function Form() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<SchemaProps>({
    resolver: zodResolver(validationSchema),
  });

  function submitForm() {
    console.log('submit');
  }

  return (
    <form className="form" onSubmit={handleSubmit(submitForm)}>
      <div>
        <label htmlFor="">Name</label>
        {errors?.name && <span>{errors.name.message}</span>}
      </div>
      <input type="text" {...register('name')} />

      <div>
        <label htmlFor="">Email</label>
        {errors?.email && <span>{errors.email.message}</span>}
      </div>
      <input type="email" {...register('email')} />

      <div>
        <label htmlFor="">Password</label>
        {errors?.password && <span>{errors.password.message}</span>}
      </div>
      <input type="Password" {...register('password')} />

      <div>
        <label htmlFor="">Phone number</label>
        {errors?.phone && <span>{errors.phone.message}</span>}
      </div>
      <input type="string" {...register('phone')} />

      <button type="submit" className="button">
        Enviar
      </button>
    </form>
  );
}
```


![form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/23gx38680vjrgfufbeir.png)

That's it guys! I hope you enjoyed reading it, and that it helped you understand a little about how it works.

Also, if you have any advices or comments on this, please leave a comment!