Внедрение проверки формы в React с нуля может стать очень запутанным, особенно когда вам нужно одновременно проверять различные типы ввода, обрабатывать ошибки и отключать кнопки.

Вот почему существуют библиотеки, которые уже обрабатывают все это за нас. В этом уроке я научу вас, как использовать библиотеки react-hook-form и zod для реализации проверки формы.

Мы будем использовать TypeScript для этого урока.

Настраивать

Начнем с инициализации нового приложения React с помощью Vite. Я буду использовать Tailwind CSS для стилизации.

Создайте новый проект с помощью Vite.

npm create vite@latest

Выберите React и Typescript.

Need to install the following packages:
  create-vite@latest
Ok to proceed? (y) y
√ Project name: ... react_form_validation
√ Select a framework: » React
√ Select a variant: » TypeScript

Перейдите в каталог проекта и установите зависимости.

cd react_form_validation
npm install 
npm run dev 

Установите и инициализируйте Tailwind.

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Настройте файл конфигурации Tailwind.

module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Добавьте это в файл index.css

@tailwind base;
@tailwind components;
@tailwind utilities;

Запустите сервер и перейдите на http://127.0.0.1:5173/, вы должны увидеть эту страницу.

Теперь давайте установим пакеты, необходимые для проекта.

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

Очистите файл App.tsx и добавьте следующий код Tailwind для регистрационной формы.

function App() {
  return (
    <section className="bg-gray-50 dark:bg-gray-900">
      <div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
        <div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
          <div className="p-6 space-y-4 md:space-y-6 sm:p-8">
            <h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
              Create and account
            </h1>
            <form className="space-y-4 md:space-y-6">
              <div>
                <label
                  htmlFor="username"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Your username
                </label>
                <input
                  type="text"
                  id="username"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
                  placeholder="Your name"
                />
              </div>
              <div>
                <label
                  htmlFor="email"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Your email
                </label>
                <input
                  type="email"
                  id="email"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
                  placeholder="[email protected]"
                />
              </div>
              <div>
                <label
                  htmlFor="password"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Password
                </label>
                <input
                  type="password"
                  id="password"
                  placeholder="••••••••"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
                />
              </div>
              <div>
                <label
                  htmlFor="confirm-password"
                  className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                >
                  Confirm password
                </label>
                <input
                  type="password"
                  id="confirmPassword"
                  placeholder="••••••••"
                  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
                />
              </div>
              <div className="flex items-start">
                <div className="flex items-center h-5">
                  <input
                    id="terms"
                    aria-describedby="terms"
                    type="checkbox"
                    className="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800"
                  />
                </div>
                <div className="ml-3 text-sm">
                  <label
                    htmlFor="terms"
                    className="font-light text-gray-500 dark:text-gray-300"
                  >
                    I accept the{" "}
                    <a
                      className="font-medium text-primary-600 hover:underline dark:text-primary-500"
                      href="#"
                    >
                      Terms and Conditions
                    </a>
                  </label>
                </div>
              </div>

              <button
                type="submit"
                className="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
              >
                Create an account
              </button>
            </form>
          </div>
        </div>
      </div>
    </section>
  );
}

export default App;

Добавьте следующее в файл конфигурации Tailwind для цветов.

module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {
      colors: {
        primary: { "50": "#eff6ff", "100": "#dbeafe", "200": "#bfdbfe", "300": "#93c5fd", "400": "#60a5fa", "500": "#3b82f6", "600": "#2563eb", "700": "#1d4ed8", "800": "#1e40af", "900": "#1e3a8a" }
      }
    },
  },
  plugins: [],
}

Вы должны увидеть эту форму в браузере.

Создание схемы проверки с помощью Zod

Во-первых, давайте импортируем все необходимые пакеты.

import { z } from "zod";
import { SubmitHandler, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";

Я собираюсь кратко объяснить, как работает Зод. Zod — это первая библиотека Typescript для создания схем проверки. Это означает, что вы можете определить, как должны выглядеть ваши данные при использовании типов Typescript. Zod может автоматически создавать типы из схемы, а также не имеет зависимостей.

Давайте начнем с создания схемы для нашей формы.

const formSchema = z
  .object({
    username: z.string().min(1, "Username is required").max(100),
    email: z.string().email("Invalid email").min(1, "Email is required"),
    password: z
      .string()
      .min(1, "Password is required")
      .min(8, "Password must have more than 8 characters"),
    confirmPassword: z.string().min(1, "Password confirmation is required"),
    terms: z.literal(true, {
      errorMap: () => ({ message: "You must accept the terms and conditions" }),
    }),
  })
  .refine((data) => data.password === data.confirmPassword, {
    path: ["confirmPassword"],
    message: "Passwords do not match",
});

Во-первых, мы объявили новую схему, используя z.object(). Здесь мы начинаем с определения типов нашего поля, в данном случае все они являются строками, поэтому мы используем z.string(). Затем мы используем функцию z.min(), чтобы указать, что нам не нужна пустая строка, также мы можем добавить сообщение об ошибке в качестве второго аргумента. Для электронной почты мы можем использовать функцию z.email(), которая поставляется с Zod.

Мы добавляем еще один z.min() для пароля, чтобы указать, что мы хотим, чтобы в нем было не менее 8 символов, и мы устанавливаем поле терминов как z.literal(), что означает, что это поле должно быть точно заданным значением.

Наконец, мы используем метод refine для реализации пользовательской проверки соответствия паролей. С помощью этого метода вы также можете реализовать настраиваемые сообщения об ошибках и более сложные проверки.

Теперь мы можем создать тип для нашей схемы, используя метод Zod infer. Мы будем использовать этот тип, чтобы сообщить react-hook-form, как должны выглядеть наши данные.

  type FormSchemaType = z.infer<typeof formSchema>;

Реализация формы React Hook

Эта библиотека поставляется с пользовательским хуком с именем useForm, это позволит нам регистрировать наши входные данные, обрабатывать отправку формы и обрабатывать ошибки.

const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<FormSchemaType>({
    resolver: zodResolver(formSchema),
});

Здесь мы деструктурировали 4 свойства из хука. Мы будем использовать register, чтобы сообщить react-hook-form, какие входные данные нужно проверять, handleSubmit — для обработки отправки формы, errors — это объект, который будет содержать все ошибки формы, а isSubmitting содержит логическое значение, которое мы можем использовать, чтобы проверить, обрабатывается ли форма в данный момент. поданный.

Мы также передаем преобразователь в хук, в этом случае мы используем нашу схему Zod для проверки формы.

Теперь давайте создадим функцию для обработки отправки формы.

const onSubmit: SubmitHandler<FormSchemaType> =  (data) => {
     console.log(data);
};

Эта функция будет вызываться при срабатывании метода onSubmit нашей формы. Пока мы просто будем записывать данные в консоль.

Регистрация полей

Мы можем использовать метод register, чтобы сообщить react-hook-form, какие поля проверять.

<input
  type="text"
  id="username"
  className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
  placeholder="Your name"
  {...register("username")}
/>

Убедитесь, что поля имеют те же значения, что и в схеме Zod. Ваша форма должна выглядеть так.

<form className="space-y-4 md:space-y-6">
  <div>
    <label
      htmlFor="username"
      className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
    >
      Your username
    </label>
    <input
      type="text"
      id="username"
      className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
      placeholder="Your name"
      {...register("username")}
    />
  </div>
  <div>
    <label
      htmlFor="email"
      className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
    >
      Your email
    </label>
    <input
      type="email"
      id="email"
      className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
      placeholder="[email protected]"
      {...register("email")}
    />
  </div>
  <div>
    <label
      htmlFor="password"
      className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
    >
      Password
    </label>
    <input
      type="password"
      id="password"
      placeholder="••••••••"
      className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
      {...register("password")}
    />
  </div>
  <div>
    <label
      htmlFor="confirmPassword"
      className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
    >
      Confirm password
    </label>
    <input
      type="password"
      id="confirmPassword"
      placeholder="••••••••"
      className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
      {...register("confirmPassword")}
    />
  </div>
  <div className="flex items-start">
    <div className="flex items-center h-5">
      <input
        id="terms"
        aria-describedby="terms"
        type="checkbox"
        className="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800"
        {...register("terms")}
      />
    </div>
    <div className="ml-3 text-sm">
      <label
        htmlFor="terms"
        className="font-light text-gray-500 dark:text-gray-300"
      >
        I accept the{" "}
        <a
          className="font-medium text-primary-600 hover:underline dark:text-primary-500"
          href="#"
        >
          Terms and Conditions
        </a>
      </label>
    </div>
  </div>

  <button
    type="submit"
    className="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
  >
    Create an account
  </button>
</form>

Отправить обработчик

С помощью метода handleSubmit мы можем вызвать обработчик onSubmit, передав его в форму.

<form
  className="space-y-4 md:space-y-6"
  onSubmit={handleSubmit(onSubmit)}
>

Теперь, когда вы заполните форму и нажмете кнопку отправки, вы сможете увидеть данные формы в консоли.

Следует отметить, что форма может быть отправлена ​​несколько раз, если вы нажмете кнопку отправки много раз подряд. Давайте исправим это, отключив кнопку отправки во время обработки нашей формы.

<button
  type="submit"
  className="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
  disabled={isSubmitting}
>
  Create an account
</button>

Вы также можете добавить стиль к кнопке, используя свойство disabled, которое есть у Tailwind.

Обработка ошибок

Последнее, что нужно сделать, это обработать ошибки, для этого мы можем использовать объект errors.

<div>
  <label
    htmlFor="email"
    className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
  >
    Your email
  </label>
  <input
    type="email"
    id="email"
    className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
    placeholder="[email protected]"
    {...register("email")}
  />
  {errors.email && (
    <span className="text-red-800 block mt-2">
      {errors.email?.message}
    </span>
  )}
</div>

Здесь мы проверяем, находится ли поле электронной почты в объекте ошибок. Если он есть, то мы отображаем сообщение под полем.

Ваша форма должна выглядеть так.

<form
  className="space-y-4 md:space-y-6"
  onSubmit={handleSubmit(onSubmit)}
>
  <div>
    <label
      htmlFor="username"
      className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
    >
      Your username
    </label>
    <input
      type="text"
      id="username"
      className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
      placeholder="Your name"
      {...register("username")}
    />
    {errors.username && (
      <span className="text-red-800 block mt-2">
        {errors.username?.message}
      </span>
    )}
  </div>
  <div>
    <label
      htmlFor="email"
      className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
    >
      Your email
    </label>
    <input
      type="email"
      id="email"
      className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
      placeholder="[email protected]"
      {...register("email")}
    />
    {errors.email && (
      <span className="text-red-800 block mt-2">
        {errors.email?.message}
      </span>
    )}
  </div>
  <div>
    <label
      htmlFor="password"
      className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
    >
      Password
    </label>
    <input
      type="password"
      id="password"
      placeholder="••••••••"
      className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
      {...register("password")}
    />
    {errors.password && (
      <span className="text-red-800 block mt-2">
        {errors.password?.message}
      </span>
    )}
  </div>
  <div>
    <label
      htmlFor="confirmPassword"
      className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
    >
      Confirm password
    </label>
    <input
      type="password"
      id="confirmPassword"
      placeholder="••••••••"
      className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg block w-full p-2.5"
      {...register("confirmPassword")}
    />
    {errors.confirmPassword && (
      <span className="text-red-800 block mt-2">
        {errors.confirmPassword?.message}
      </span>
    )}
  </div>
  <div className="flex items-start">
    <div className="flex items-center h-5">
      <input
        id="terms"
        aria-describedby="terms"
        type="checkbox"
        className="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800"
        {...register("terms")}
      />
    </div>
    <div className="ml-3 text-sm">
      <label
        htmlFor="terms"
        className="font-light text-gray-500 dark:text-gray-300"
      >
        I accept the{" "}
        <a
          className="font-medium text-primary-600 hover:underline dark:text-primary-500"
          href="#"
        >
          Terms and Conditions
        </a>
      </label>
    </div>
  </div>
  {errors.terms && (
    <span className="text-red-800 block mt-2">
      {errors.terms?.message}
    </span>
  )}
  <button
    type="submit"
    className="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
    disabled={isSubmitting}
  >
    Create an account
  </button>
</form>

Тестирование формы

Теперь мы можем проверить нашу форму.

Если мы попытаемся отправить форму, не заполнив ее, мы увидим, что появляются необходимые сообщения об ошибках.

Проверка пароля тоже работает.

И наши данные записываются в консоль, как и ожидалось.

Заключение

Теперь вы знаете, как реализовать проверку формы в React с помощью библиотек react-hook-form и zod. Я надеюсь, что теперь вы видите ценность использования библиотек проверки формы и то, как они помогают нам экономить время и улучшать наш код. Теперь, если вам нужно повторно использовать логику проверки формы, вы можете перевести ее в Bit, чтобы вы могли использовать ее позже или поделиться ею с товарищами по команде.

Я оставляю здесь ссылку на репозиторий GitHub, если вы хотите проверить готовый код.

Спасибо за чтение!

Создавайте приложения с многократно используемой логикой

Инструмент с открытым исходным кодом Bit помогает более чем 250 000 разработчиков создавать приложения с компонентами.

Превратите любой пользовательский интерфейс, функцию или страницу в компонент многократного использования — и поделитесь им со своими приложениями. Легче сотрудничать и строить быстрее.

Разделите приложения на компоненты, чтобы упростить разработку приложений, и наслаждайтесь наилучшими возможностями для рабочих процессов, которые вы хотите:

Микро-интерфейсы

Система дизайна

Совместное использование кода и повторное использование

Монорепо

Узнать больше