Validación de formularios con React Hook Forms y Zod

Validación de formularios con React Hook Forms y Zod

Como desarrolladores Frontend una parte importante en nuestro trabajo es hacer peticiones al Backend, ya sea una solicitud de datos, una comprobación de status o actualizar/crear información, esta última es importante porque no podemos enviar cualquier cosa esperando una respuesta exitosa. Por ejemplo imagina un formulario para iniciar sesión que pide correo y contraseña, fácilmente podemos hacer rápido un formulario que registre los input y mandar la información por una petición, pero de esta manera podemos cometer muchos fallos ya sea permitiendo que envié cualquier texto como correo o una contraseña de solo 3 caracteres, si bien el Backend tiene validaciones de su lado no es recomendable dejarle toda la responsabilidad, pues no solo usamos recursos innecesarios sino que hacemos creer al usuario que la información que proporcionó es válida cuando va a ser rechazada de todos modos.

Con estos puntos en mente es necesario tener un buen sistema de formularios para evitar esos inconvenientes, para eso te mostraré cómo con 2 sencillas librerías puedes ahorrarte estos dolores de cabeza

React Hook Form

RHF es una de las librerías más populares del entorno React, que nos permite tener un mayor control sobre los formularios, como saber cuando se cambia un valor, si los datos ya son validos para hacer el submit, entre otros métodos y hooks que facilitan nuestra experiencia de desarrollo.

Zod

Probablemente la librería más popular para validar tipos, objetos y schemas en general de Javascript, pudiendo comprobar si los datos que dimos son los correctos y flexible para declarar tipos de datos. No solo se usa del lado del cliente, es popular para validar datos en el propio backend.

Instalar dependencias

npm install react-hook-form zod @hookform/resolvers

@hookform/resolvers nos ayudará a combinar Zod con RHF

Hook para validar formularios

Este hook nos servirá a no repetir el mismo patrón en cada formulario, así solamente tendremos que invocarlo y enviarle el esquema a validar

// useZForm.ts
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { DefaultValues, useForm } from "react-hook-form";

export interface useZFormProps<T> {
  schema: z.ZodType<T>;
  defaultValues: DefaultValues<T>;
}

const useZForm = <T extends Object>({
  schema,
  defaultValues,
}: useZFormProps<T>) => {
  const methods = useForm<T>({
    resolver: zodResolver(schema),
    defaultValues,
  });

  return { methods, ...methods };
};

export default useZForm;

Donde:

  • useZFormProps definimos que queremos el esquema a evaluar durante el ciclo de vida del formulario y los valores por defecto de éste, el cual es opcional
  • methods es el resultado del hook de RHF donde recibimos todos los métodos y valores necesarios para empezar a trabajar en nuestro formulario
  • return { methods, ...methods } para mas facilidad regreso el methods y la desestructuración de este para no repetir methods en cada llamada

FormProvider

Ahora vamos a crear un componente que dentro del children se maneje el formulario

// FormProvider.tsx
import { FormProvider as Form, UseFormReturn } from "react-hook-form";

type FPProps = {
  children: React.ReactNode;
  methods: UseFormReturn<any>;
  onSubmit?: VoidFunction;
};

export default function FormProvider ({ children, onSubmit, methods }: FPProps) {
    return (
        <Form {...methods}>
            <form onSubmit={onSubmit}>
                {children}
            <form/>
        </Form>
    )
}

Con este sencillo componente podemos empezar a poner nuestros inputs, pero falta añadir el control de los inputs

RHFInput

// RHFInput.tsx

import { Controller, useFormContext } from "react-hook-form";

type Props = HTMLInputElement & {
  name: string;
};

export default function RHFInput({ name, helperText, ...other }: Props) {
  const { control } = useFormContext();

  return (
    <Controller
      name={name}
      control={control}
      render={({ field }) => (
        <input
          {...field}
          value={
            typeof field.value === "number" && field.value === 0
              ? ""
              : field.value
          }
          {...other}
        />
      )}
    />
  );
}

Este componente nos permitirá fácilmente controlar el input del usuario

Formulario de login

Con estos componentes ahora podemos hacer un formulario seguro para el usuario

// Login.tsx
import { z } from "zod";
import {useZForm, FormProvider, RHFInput} from '@/components/form';

export default Login () {
    const formSchema = z.object({
        email: z.string().email({ message: "Correo no valido" }),
        password: z.string().min(8,{ message: "La contraseña debe ser de mínimo 8 caracteres" })
    })

    const {
        methods,
        handleSubmit,
        formState: { isValid, isSubmitting },
    } = useZForm({ schema: formSchema, defaultValues: { email: '', password: '' } })

    const onSubmit = ({ email, password }: z.infer<typeof formSchema>) => {
        // Código para enviar los datos al back y recibir el usuario o error de login
    }

    return(
        <FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
            <RHFInput name="email" placeholder="correo@ejemplo.com"/>
            <RHFInput name="password" placeholder="*****"/>
            <button type="submit" disabled={!isValid}>Iniciar sesión</button>
        </FormProvider>
    )
}

Conclusión

React Hook Form y Zod son excelentes herramientas que facilitan la experiencia del usuario y del desarrollador, no solo son flexibles sino muy fáciles de integrar, hace ya mas de un año que empecé a utilizar este combo tanto en mi trabajo como en proyectos personales y sin duda alguna es sorprendente lo sencillo que se volvió para mi trabajar con formularios, si tienes la oportunidad de integrarlo a tus proyectos no no te arrepentirás.

Recuerda visitar la documentación de estas librerías para estar mejor informado: