Это краткий пример того, как настроить проверку формы в Next.js с библиотекой форм React Hook.

Первоначально опубликовано здесь https://tkssharma.com/nextjs-with-react-hook-forms-building-forms/

React Hook Form — это библиотека для работы с формами в React с использованием React Hooks, я наткнулся на нее около года назад и с тех пор использую в своих проектах Next.js и React, думаю, ее проще использовать, чем другие варианты доступен и требует меньше кода. Для получения дополнительной информации см. https://react-hook-form.com.

Установка Для установки React Hook Form требуется всего одна команда, и вы готовы к работе.

npm install react-hook-form

Пример Следующий фрагмент кода демонстрирует базовый пример использования:

import React from 'react';
import { useForm } from 'react-hook-form';
export default function App() {
  const {
    register,
    handleSubmit,
    watch,
    formState: { errors },
  } = useForm();
  const onSubmit = (data) => console.log(data);
  console.log(watch('example')); // watch input value by passing the name of it
  return (
    /* "handleSubmit" will validate your inputs before invoking "onSubmit" */
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* register your input into the hook by invoking the "register" function */}
      <input defaultValue="test" {...register('example')} />
      {/* include validation with required or other standard HTML validation rules */}
      <input {...register('exampleRequired', { required: true })} />
      {/* errors will return when field validation fails  */}
      {errors.exampleRequired && <span>This field is required</span>}
      <input type="submit" />
    </form>
  );
}

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

Вот он в действии: (см. CodeSandbox на https://codesandbox.io/s/next-js-form-validation-example-forked-1e2soi)

Домашняя страница Next.js с формой React Hook На домашней странице находится пример регистрационной формы, созданной с помощью библиотеки форм React Hook.

Правила проверки формы определяются с помощью библиотеки проверки схемы Yup и передаются функции использования формы React Hook Form, для получения дополнительной информации о Yup см. https://github.com/jquense/yup.

Правила проверки формы определяются с помощью библиотеки проверки схемы Yup и передаются с помощью formOptions в функцию React Hook Form useForm(), для получения дополнительной информации о Yup см. https://github.com/jquense/yup.

Хук-функция useForm() возвращает объект с методами для работы с формой, включая регистрацию ввода, обработку отправки формы, сброс формы, доступ к состоянию формы, отображение ошибок и многое другое, полный список см. https://react-hook -form.com/api/useform.

Метод onSubmit() вызывается, когда форма действительна и отправлена, и просто отображает данные формы в предупреждении javascript.

Возвращенный шаблон JSX содержит форму со всеми полями ввода и сообщениями проверки. Поля формы регистрируются в форме React Hook путем вызова функции регистрации с именем поля из каждого элемента ввода (например, {…register(‘title’)}).

Чтобы установить React Hook Form, выполните следующую команду:

npm install react-hook-form

Как использовать React Hooks в форме В этом разделе вы узнаете об основах useForm Hook, создав очень простую регистрационную форму.

Сначала импортируйте хук useForm из пакета react-hook-form:

import { useForm } from "react-hook-form";
// Then, inside your component, use the Hook as follows:
const { register, handleSubmit } = useForm();

Хук useForm возвращает объект, содержащий несколько свойств. На данный момент вам требуется только регистрация и handleSubmit.

Метод register помогает зарегистрировать поле ввода в форме React Hook, чтобы оно было доступно для проверки, а его значение можно было отслеживать на предмет изменений.

Чтобы зарегистрировать ввод, мы передадим метод register в поле ввода как таковой:

<input type="text" name="firstName" {...register('firstName')} />

Этот синтаксис оператора расширения является новой реализацией библиотеки, которая обеспечивает строгую проверку типов в формах с помощью TypeScript. Вы можете узнать больше о строгой проверке типов в React Hook Form здесь.

В версиях старше v7 метод register был прикреплен к атрибуту ref как таковой:

<input type="text" name="firstName" ref={register} />

Обратите внимание, что входной компонент должен иметь имя, а его значение должно быть уникальным.

Метод handleSubmit, как следует из названия, управляет отправкой формы. Его нужно передать как значение свойства onSubmit компонента формы.

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

const onFormSubmit  = data => console.log(data);
const onErrors = errors => console.error(errors);
<form onSubmit={handleSubmit(onFormSubmit, onErrors)}>
 {/* ... */}
</form>

Теперь, когда у вас есть четкое представление об основном использовании хука useForm, давайте взглянем на более реалистичный пример:

import React from "react";
import { useForm } from "react-hook-form";
const RegisterForm = () => {
  const { register, handleSubmit } = useForm();
  const handleRegistration = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(handleRegistration)}>
      <div>
        <label>Name</label>
        <input name="name" {...register('name')} />
      </div>
      <div>
        <label>Email</label>
        <input type="email" name="email" {...register('email')} />
      </div>
      <div>
        <label>Password</label>
        <input type="password" name="password" {...register('password')} />
      </div>
      <button>Submit</button>
    </form>
  );
};
export default RegisterForm;

Как видите, никакие другие компоненты для отслеживания входных значений не импортировались. Хук useForm делает код компонента чище и проще в обслуживании, а поскольку форма не контролируется, вам не нужно передавать реквизиты, такие как onChange и value, для каждого ввода.

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

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

Как проверять формы с помощью React Hook Form

Чтобы применить проверки к полю, вы можете передать параметры проверки в метод регистрации. Параметры проверки аналогичны существующему стандарту проверки формы HTML.

Эти параметры проверки включают следующие свойства:

required указывает, является ли поле обязательным или нет. Если для этого свойства установлено значение true, то поле не может быть пустым. minlength и maxlength устанавливают минимальную и максимальную длину для строкового входного значения. min и max устанавливают минимальное и максимальное значения для числового значения. это может быть электронная почта, число, текст или любые другие стандартные типы ввода HTML.

<input name="name" type="text" {...register('name', { required: true } )} />

Теперь попробуйте отправить форму с пустым полем. Это приведет к следующему объекту ошибки:

{
name: {
  type: "required",
  message: "",
  ref: <input name="name" type="text" />
  }
}

Здесь свойство type относится к типу неудачной проверки, а свойство ref содержит собственный элемент ввода DOM.

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

<form onSubmit={handleSubmit(handleRegistration, handleError)}>
  <div>
      <label>Name</label>
      <input name="name" {...register('name', { required: "Name is required" } )} />
  </div>
</form>

Затем получите доступ к объекту ошибок с помощью хука useForm:

const { register, handleSubmit, formState: { errors } } = useForm();
You can display errors to your users like so:
//  {errors?.name && errors.name.message}
const RegisterForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const handleRegistration = (data) => console.log(data);
  return (
    <form onSubmit={handleSubmit(handleRegistration)}>
      <div>
        <label>Name</label>
        <input type="text" name="name" {...register('name')} />
        {errors?.name && errors.name.message}
      </div>
      {/* more input fields... */}
      <button>Submit</button>
    </form>
  );
};

Ниже вы можете найти полный пример:

import React from "react";
import { useForm } from "react-hook-form";
const RegisterForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const handleRegistration = (data) => console.log(data);
  const handleError = (errors) => {};
  const registerOptions = {
    name: { required: "Name is required" },
    email: { required: "Email is required" },
    password: {
      required: "Password is required",
      minLength: {
        value: 8,
        message: "Password must have at least 8 characters"
      }
    }
  };
  return (
    <form onSubmit={handleSubmit(handleRegistration, handleError)}>
      <div>
        <label>Name</label>
        <input name="name" type="text" {...register('name', registerOptions.name) }/>
        <small className="text-danger">
          {errors?.name && errors.name.message}
        </small>
      </div>
      <div>
        <label>Email</label>
        <input
          type="email"
          name="email"
          {...register('email', registerOptions.email)}
        />
        <small className="text-danger">
          {errors?.email && errors.email.message}
        </small>
      </div>
      <div>
        <label>Password</label>
        <input
          type="password"
          name="password"
          {...register('password', registerOptions.password)}
        />
        <small className="text-danger">
          {errors?.password && errors.password.message}
        </small>
      </div>
      <button>Submit</button>
    </form>
  );
};
export default RegisterForm;

Если вы хотите проверить поле при возникновении события onChange или onBlur, вы можете передать свойство режима в хук useForm:

const { register, handleSubmit, errors } = useForm({
  mode: "onBlur"
});

React Hook Form предоставляет компонент контроллера-оболочки, который позволяет вам регистрировать контролируемый внешний компонент, аналогично тому, как работает метод регистрации. В этом случае вместо метода register вы будете использовать объект управления из хука useForm:

const { register, handleSubmit, control } = useForm();

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

Объект управления должен быть передан в элемент управления компонента Controller вместе с именем поля. Вы можете указать правила проверки, используя свойство rules.

Управляемый компонент должен быть передан компоненту контроллера с помощью реквизита as. Компоненту Select также требуется свойство options для отображения раскрывающихся опций:

<Controller
  name="role"
  control={control}
  defaultValue=""
  rules={registerOptions.role}
  render={({ field }) => (
    <Select options={selectOptions} {...field} label="Text field" />
  )}
/>

Приведенная выше поддержка рендеринга предоставляет onChange, onBlur, имя, ссылку и значение дочернему компоненту. Распространяя поле на компонент Select, React Hook Form регистрирует поле ввода.

Вы можете проверить полный пример для поля роли ниже:

import { useForm, Controller } from "react-hook-form";
import Select from "react-select";
// ...
const { register, handleSubmit, errors, control } = useForm({
  // use mode to specify the event that triggers each input field 
  mode: "onBlur"
});
const selectOptions = [
  { value: "student", label: "Student" },
  { value: "developer", label: "Developer" },
  { value: "manager", label: "Manager" }
];
const registerOptions = {
  role: { required: "Role is required" }
};
<form>
  <div>
    <label>Your Role</label>
    <Controller
      name="role"
      control={control}
      defaultValue=""
      rules={registerOptions.role}
      render={({ field }) => (
        <Select options={selectOptions} {...field} label="Text field" />
      )}
    />
    <small className="text-danger">
      {errors?.role && errors.role.message}
    </small>
  </div>
</form>

Вы также можете просмотреть справку по API для компонента Controller здесь для подробного объяснения.

Давайте проверим еще несколько примеров с yup для проверки

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as Yup from 'yup';
export default Home;
function Home() {
  // form validation rules
  const validationSchema = Yup.object().shape({
    title: Yup.string().required('Title is required'),
    firstName: Yup.string().required('First Name is required'),
    lastName: Yup.string().required('Last name is required'),
    dob: Yup.string()
      .required('Date of Birth is required')
      .matches(
        /^\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/,
        'Date of Birth must be a valid date in the format YYYY-MM-DD'
      ),
    email: Yup.string()
      .required('Email is required')
      .email('Email is invalid'),
    password: Yup.string()
      .min(6, 'Password must be at least 6 characters')
      .required('Password is required'),
    confirmPassword: Yup.string()
      .oneOf([Yup.ref('password'), null], 'Passwords must match')
      .required('Confirm Password is required'),
    acceptTerms: Yup.bool().oneOf([true], 'Accept Ts & Cs is required'),
  });
  const formOptions = { resolver: yupResolver(validationSchema) };
  // get functions to build form with useForm() hook
  const { register, handleSubmit, reset, formState } = useForm(formOptions);
  const { errors } = formState;
  function onSubmit(data) {
    // display form data on success
    alert('SUCCESS!! :-)\n\n' + JSON.stringify(data, null, 4));
    return false;
  }
  return (
    <div className="card m-3">
      <h5 className="card-header">Next.js - Form Validation Example</h5>
      <div className="card-body">
        <form onSubmit={handleSubmit(onSubmit)}>
          <div className="form-row">
            <div className="form-group col">
              <label>Title</label>
              <select
                name="title"
                {...register('title')}
                className={`form-control ${errors.title ? 'is-invalid' : ''}`}
              >
                <option value=""></option>
                <option value="Mr">Mr</option>
                <option value="Mrs">Mrs</option>
                <option value="Miss">Miss</option>
                <option value="Ms">Ms</option>
              </select>
              <div className="invalid-feedback">{errors.title?.message}</div>
            </div>
            <div className="form-group col-5">
              <label>First Name</label>
              <input
                name="firstName"
                type="text"
                {...register('firstName')}
                className={`form-control ${errors.firstName ? 'is-invalid' : ''}`}
              />
              <div className="invalid-feedback">{errors.firstName?.message}</div>
            </div>
            <div className="form-group col-5">
              <label>Last Name</label>
              <input
                name="lastName"
                type="text"
                {...register('lastName')}
                className={`form-control ${errors.lastName ? 'is-invalid' : ''}`}
              />
              <div className="invalid-feedback">{errors.lastName?.message}</div>
            </div>
          </div>
          <div className="form-row">
            <div className="form-group col">
              <label>Date of Birth</label>
              <input
                name="dob"
                type="date"
                {...register('dob')}
                className={`form-control ${errors.dob ? 'is-invalid' : ''}`}
              />
              <div className="invalid-feedback">{errors.dob?.message}</div>
            </div>
            <div className="form-group col">
              <label>Email</label>
              <input
                name="email"
                type="text"
                {...register('email')}
                className={`form-control ${errors.email ? 'is-invalid' : ''}`}
              />
              <div className="invalid-feedback">{errors.email?.message}</div>
            </div>
          </div>
          <div className="form-row">
            <div className="form-group col">
              <label>Password</label>
              <input
                name="password"
                type="password"
                {...register('password')}
                className={`form-control ${errors.password ? 'is-invalid' : ''}`}
              />
              <div className="invalid-feedback">{errors.password?.message}</div>
            </div>
            <div className="form-group col">
              <label>Confirm Password</label>
              <input
                name="confirmPassword"
                type="password"
                {...register('confirmPassword')}
                className={`form-control ${errors.confirmPassword ? 'is-invalid' : ''}`}
              />
              <div className="invalid-feedback">{errors.confirmPassword?.message}</div>
            </div>
          </div>
          <div className="form-group form-check">
            <input
              name="acceptTerms"
              type="checkbox"
              {...register('acceptTerms')}
              id="acceptTerms"
              className={`form-check-input ${errors.acceptTerms ? 'is-invalid' : ''}`}
            />
            <label htmlFor="acceptTerms" className="form-check-label">
              Accept Terms & Conditions
            </label>
            <div className="invalid-feedback">{errors.acceptTerms?.message}</div>
          </div>
          <div className="form-group">
            <button type="submit" className="btn btn-primary mr-1">
              Register
            </button>
            <button type="button" onClick={() => reset()} className="btn btn-secondary">
              Reset
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

Другой пример

export function InputForm({ services, handleFormSubmit, servicesLocations, serviceType, userProfile }: inputProps) {
  const validationSchema = Yup.object().shape({
    orderBy: Yup.string().required('order by is required'),
    phoneNumber: Yup.string().required('phone is required'),
    orderDate: Yup.string().required('order date is required'),
    regarding: Yup.string().required('regarding is required'),
  });
  const formOptions: any = { resolver: yupResolver(validationSchema), reValidateMode: 'onChange', mode: 'onChange' };
  const { register, getValues, handleSubmit, formState } = useForm<any>(formOptions);
  const { errors } = formState;
  const onSubmit = (e: any) => {
    const { orderDate, orderBy, instructions, extension, regarding, phoneNumber } = getValues();
    // submit
  };
 return (
   <form onSubmit={handleSubmit(onSubmit)} className="px-4 py-5 sm:p-6">
              <div className="grid grid-cols-6 gap-6 pb-8 border-b-2 border-pc-base-black">
                <div className="col-span-6 sm:col-span-3">
                  <label className="block text-sm font-bold text-gray-800 font-source">Ordered By</label>
                  <input
                    defaultValue={userProfile.name}
                    className={`w-full p-2 mt-2 border-2 rounded shadow-sm outline-none border-pc-base-black focus:border-gray-300 ${errors.orderBy ? 'is-invalid' : ''
                      }`}
                    {...register('orderBy')}
                    type="text"
                    name="orderBy"
                    id="orderBy"
                    readOnly
                    autoComplete="orderBy"
                  />
                  {errors.orderBy?.message && <FormError errorMessage={errors.orderBy?.message} />}
                </div>
                <div className="col-span-6 sm:col-span-3">
                  <label className="block text-sm font-bold text-gray-700 font-source">Ordered date</label>
                  <input
                    {...register('orderDate')}
                    defaultValue={getToday()}
                    type="text"
                    name="orderDate"
                    readOnly
                    id="orderDate"
                    autoComplete="orderDate"
                    className={`w-full p-2 mt-2 border-2 rounded shadow-sm outline-none border-pc-base-black focus:border-gray-300 ${errors.orderDate ? 'is-invalid' : ''
                      }`}
                  />
                  {errors.orderDate?.message && <FormError errorMessage={errors.orderDate?.message} />}
                </div>
                <div className="col-span-6 sm:col-span-3">
                  <label className="block text-sm font-bold text-gray-700 font-source">Phone Number</label>
                  <input
                    defaultValue={userProfile.phoneNumber}
                    {...register('phoneNumber')}
                    type="text"
                    name="phoneNumber"
                    readOnly
                    id="phoneNumber"
                    autoComplete="phoneNumber"
                    className={`w-full p-2 mt-2 border-2 rounded shadow-sm outline-none border-pc-base-black focus:border-gray-300 ${errors.phoneNumber ? 'is-invalid' : ''
                      }`}
                  />
                  {errors.phoneNumber?.message && <FormError errorMessage={errors.phoneNumber?.message} />}
                </div>
                <div className="col-span-6 sm:col-span-3">
                  <label className="block text-sm font-bold text-gray-700 font-source">Extension</label>
                  <input
                    type="text"
                    name="extension"
                    id="extension"
                    autoComplete="extension"
                    className="w-full p-2 mt-2 border-2 rounded shadow-sm outline-none border-pc-base-black focus:border-gray-300"
                  />
                </div>
                <div className="col-span-6 sm:col-span-3">
                  <label className="block text-sm font-bold text-gray-700 font-source">Medical Record Number</label>
                  <input
                    {...register('regarding')}
                    type="text"
                    name="regarding"
                    id="regarding"
                    placeholder='MRN #'
                    autoComplete="regarding"
                    className={`w-full p-2 mt-2 border-2 rounded shadow-sm outline-none border-pc-base-black focus:border-gray-300 ${errors.regarding ? 'is-invalid' : ''
                      }`}
                  />
                  {errors.regarding?.message && <FormError errorMessage={errors.regarding?.message} />}
                </div>
                <div className="col-span-6 sm:col-span-3">
                  <label className="block text-sm font-bold text-gray-700 font-source">Special Instructions</label>
                  <input
                    type="text"
                    name="instructions"
                    id="instructions"
                    autoComplete="instructions"
                    className="w-full p-2 mt-2 border-2 rounded shadow-sm outline-none border-pc-base-black focus:border-gray-300"
                  />
                </div>
              </div>
            </div>
          </form>
 )

Заключение

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

Рекомендации