React Hook Form: создание сложных форм для начинающих

658f548d02e7584bff41af3ab472e646.png

Привет, Хабр!

Сегодня рассмотрим важную тему для всех, кто занимается созданием сложных и многошаговых форм в React. Мы все знаем, как это бывает: бесконечные рендеры, тонны кода для валидации и управления состоянием, а также бесконечная борьба за оптимизацию производительности. Но никто уже давно не отчаивается, ведь существует мощное и гибкое решение React Hook Form.

React Hook Form — это библиотека, которая использует концепцию неконтролируемых компонентов, чтобы минимизировать количество повторных рендеров и повысить производительность приложения.

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

Создание сложных форм

Разделение формы на компоненты

Разделение формы на компоненты — это основа, которая позволяет создавать многошаговые формы. Каждый шаг формы может быть представлен отдельным компонентом:

// Step 1: PersonalInfo.js
import React from 'react';
import { useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';

const PersonalInfo = ({ onSubmit }) => {
  const { register, handleSubmit } = useForm();
  const history = useHistory();

  const handleNext = (data) => {
    onSubmit(data);
    history.push('/employment');
  };

  return (
    
); }; export default PersonalInfo;

Контекст формы для управления состоянием данных

Контекст формы позволяетпередавать состояние формы между различными компонентами. Это можно сделать с помощью FormProvider и useFormContext:

// FormContext.js
import React, { createContext, useContext, useState } from 'react';

const FormContext = createContext();

export const useFormData = () => useContext(FormContext);

export const FormProvider = ({ children }) => {
  const [formData, setFormData] = useState({});

  const updateFormData = (data) => {
    setFormData((prev) => ({ ...prev, ...data }));
  };

  return (
    
      {children}
    
  );
};

Пример создания шагов формы и их связка через React Router

Через React Router можно управлять навигацией между различными шагами формы:

// App.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { FormProvider } from './FormContext';
import PersonalInfo from './PersonalInfo';
import Employment from './Employment';
import Review from './Review';

const App = () => (
  
    
      
        
        
        
      
    
  
);

export default App;
// Step 2: Employment.js
import React from 'react';
import { useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';
import { useFormData } from './FormContext';

const Employment = () => {
  const { register, handleSubmit } = useForm();
  const history = useHistory();
  const { updateFormData } = useFormData();

  const handleNext = (data) => {
    updateFormData(data);
    history.push('/review');
  };

  return (
    
); }; export default Employment;

Подключение и управление состоянием формы с помощью хуков

Хуки useForm, useFormContext и другие позволяет управлять состоянием формы и валидировать данные:

// Step 3: Review.js
import React from 'react';
import { useFormData } from './FormContext';

const Review = () => {
  const { formData } = useFormData();

  const handleSubmit = () => {
    console.log('Form submitted:', formData);
    // Add submission logic here
  };

  return (
    

Review Your Information

{JSON.stringify(formData, null, 2)}
); }; export default Review;

Валидация и обработка ошибок

Рассмотрим настройку встроенной валидации.

Можно настривать валидацию с помощью атрибутов required, minLength, maxLength, pattern, и validate.

Пример:

import React from 'react';
import { useForm } from 'react-hook-form';

const SimpleForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    
{errors.name &&

{errors.name.message}

}
{errors.email &&

{errors.email.message}

}
); }; export default SimpleForm;

Интеграция с библиотеками для схемной валидации

Для более хардовой валидации можно использовать библиотеки Yup и Zod. Эти библиотеки позволяют создавать схемы валидации и интегрировать их с React Hook Form.

Пример с Yup:

import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';

const schema = yup.object().shape({
  name: yup.string().required('Name is required'),
  email: yup.string().email('Invalid email').required('Email is required'),
});

const YupForm = () => {
  const { control, handleSubmit, formState: { errors } } = useForm({
    resolver: yupResolver(schema),
  });

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    
} /> {errors.name &&

{errors.name.message}

}
} /> {errors.email &&

{errors.email.message}

}
); }; export default YupForm;

Обработка и отображение ошибок

Можно управлять отображением ошибок с помощью объекта errors, предоставляемого хукем useForm, пример:

import React from 'react';
import { useForm } from 'react-hook-form';

const ErrorHandlingForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    
{errors.username &&

{errors.username.message}

}
{errors.password &&

{errors.password.message}

}
); }; export default ErrorHandlingForm;

Примеры для различных типов валидации

Пример валидации формы регистрации:

import React from 'react';
import { useForm } from 'react-hook-form';

const RegistrationForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log('Registration Data:', data);
  };

  return (
    
{errors.firstName &&

{errors.firstName.message}

}
{errors.lastName &&

{errors.lastName.message}

}
{errors.email &&

{errors.email.message}

}
{errors.password &&

{errors.password.message}

}
); }; export default RegistrationForm;

Пример кастомной валидации пароля:

import React from 'react';
import { useForm } from 'react-hook-form';

const CustomValidationForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const validatePassword = (value) => {
    if (value.length < 8) {
      return 'Password must be at least 8 characters long';
    } else if (!/[A-Z]/.test(value)) {
      return 'Password must contain at least one uppercase letter';
    } else if (!/[0-9]/.test(value)) {
      return 'Password must contain at least one number';
    }
    return true;
  };

  const onSubmit = (data) => {
    console.log('Form Data:', data);
  };

  return (
    
{errors.password &&

{errors.password.message}

}
); }; export default CustomValidationForm;

Прочие фичи

useFieldArray для динамического добавления/удаления полей формы

useFieldArray — это мощный хук в React Hook Form, который позволяет динамически управлять массивом полей формы. Мастхев для создания форм, в которых пользователи могут добавлять или удалять элементы.

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

import React from 'react';
import { useForm, useFieldArray } from 'react-hook-form';

const TeamForm = () => {
  const { register, control, handleSubmit } = useForm({
    defaultValues: {
      members: [{ name: '' }],
    },
  });
  const { fields, append, remove } = useFieldArray({
    control,
    name: 'members',
  });

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    

Team Members

{fields.map((field, index) => (
))}
); }; export default TeamForm;

Используем useForm для управления состоянием формы и useFieldArray для динамического управления массивом полей. Кнопки »Add Member» и »Remove» позволяют юзеру добавлять и удалять поля соответственно.

Оптимизация производительности форм

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

  1. Избегайте ненужных рендеров: используйте React.memo и useMemo, чтобы избежать повторных рендеров компонентов, которые не зависят от состояния формы.

  2. Контролируемые и неконтролируемые компоненты: React Hook Form использует неконтролируемые компоненты по дефолту, что снижает количество рендеров. Если нужно использовать контролируемые компоненты, убеждаемся, что мы оптимизируем их правильно.

  3. Ленивая загрузка компонентов: загружаем компоненты формы по мере необходимости, а не все сразу.

Пример:

import React, { memo } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { TextField, Button } from '@material-ui/core';

const LargeForm = () => {
  const { control, handleSubmit } = useForm();
  
  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    
{Array.from({ length: 100 }).map((_, index) => ( } /> ))} ); }; export default memo(LargeForm);

Интеграция с внешними компонентами UI

React Hook Form легко интегрируется с внешними библиотеками компонентов UI, такими как Material-UI и Ant Design.

Пример кода с Material-UI:

import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import { TextField, Button } from '@material-ui/core';

const MaterialUIForm = () => {
  const { control, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    
( )} /> ( )} /> ); }; export default MaterialUIForm;

С React Hook Form создание сложных форм в React становится значительно проще!

Все актуальные методы и инструменты программирования можно освоить на онлайн-курсах OTUS: в каталоге можно посмотреть список всех программ, а в календаре — записаться на открытые уроки.

© Habrahabr.ru