Как я использую React Hook Form

от автора

Приветствую, уважаемые читатели! Сегодня я хочу поделиться своим опытом использования одной из самых популярных библиотек для создания форм на React — React Hook Form. Когда я только начинал использовать эту замечательную библиотеку, я совершил несколько ошибок, которые я надеюсь, вы сможете избежать.

Используемые библеотеки

  1. React 18.2.0

  2. React Hook Form v7.45.1

  3. Material UI v5.13.7

  4. Axios v1.4.0

  5. JSON server v0.17.3

Создание и заполнение формы

В этой статье мы создадим форму для добавления и редактирования пользователей. Давайте начнем со следующего кода:

import { Button, TextField } from "@mui/material"; import { Controller, FormProvider, useForm } from "react-hook-form"; import "./App.css"  export const App = () => {   const methods = useForm()    const { control, handleSubmit } = methods    const onSave = (data) => {     console.log(data)   }    return (     <FormProvider {...methods}>       <div className="card">         <span>Пользователь</span>         <Controller           name="name"           control={control}           render={({ field: { value, onChange } }) => (             <TextField               value={value}               onChange={onChange}             />           )}         />         <Controller           name="suname"           control={control}           render={({ field: { value, onChange } }) => (             <TextField               value={value}               onChange={onChange}             />           )}         />       </div>       <Button onClick={handleSubmit(onSave)}>Сохранить</Button>     </FormProvider>   ); } 

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

Мы оборачиваем нашу форму в FormProvider и передаем все методы, которые мы получили из useForm, как пропсы.

Для регистрации полей воспользуемся компонентом Controller, предоставляемым React Hook Form.

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

import { TextField } from "@mui/material" import { Controller, useFormContext } from "react-hook-form"  export const UserCard = () => {     const { control } = useFormContext()      return (         <div className="card">             <span>Пользователь</span>             <Controller                 name="name"                 control={control}                 render={({ field: { value, onChange } }) => (                     <TextField                       value={value}                       onChange={onChange}                     />                 )}             />             <Controller                 name="suname"                 control={control}                 render={({ field: { value, onChange } }) => (                     <TextField                       value={value}                       onChange={onChange}                     />                 )}             />       </div>     ) }

Обратите внимание, что при использовании Controller нам также нужно передать control из нашей формы. Но если мы вызываем useForm снова, мы создаем новую форму. Чтобы получить методы в контексте той же формы, можно использовать хук useFormContext. Он возвращает те же методы, что и useForm, но уже в контексте нашей формы, благодаря тому, что форма обернута в FormProvider. Таким образом, находясь на любом уровне внутри нашей формы, мы всегда можем получить все ее методы.

Вот как теперь выглядит наша форма:

import { Button } from "@mui/material"; import { FormProvider, useForm } from "react-hook-form"; import "./App.css" import { UserCard } from "./UserCard";  export const App = () => {   const methods = useForm()    const { handleSubmit } = methods    const onSave = (data) => {     console.log(data)   }    return (     <FormProvider {...methods}>       <UserCard />       <Button onClick={handleSubmit(onSave)}>Сохранить</Button>     </FormProvider>   ); } 

Учимся работать с массивами в форме

Поскольку у нас будет массив пользователей, форма не совсем корректна. В данный момент у нас есть всего два поля. А состояние нашей формы должно содержать массив объектов user с полями name и surname. Мы будем запрашивать пользователей через API, для этого я воспользуюсь JSON-server и создам несколько пользователей.

{     "users": [         {             "id": 1,             "name": "Artem",             "suname": "Morozov"         },         {             "id": 2,             "name": "Maxim",             "suname": "Klever"         },         {             "id": 3,             "name": "John",             "suname": "Weelson"         }     ] }

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

import { useEffect } from "react" import { Button } from "@mui/material"; import { FormProvider, useFieldArray, useForm } from "react-hook-form"; import "./App.css" import { UserCard } from "./UserCard"; import axios from "axios";  export const App = () => {   const methods = useForm({     defaultValues: {       users: []     }   })    const { control, handleSubmit, reset } = methods    const { fields } = useFieldArray({     name: "users",     control: control,     shouldUnregister: true   })    const onSave = (data) => {     console.log(data)   }    useEffect(() => {     const getUsersAsync = async () => {       const { data } = await axios.get("http://localhost:3000/users")       reset({         users: data       })     }     getUsersAsync()   }, [reset])    return (     <FormProvider {...methods}>       {fields.map((user, index) => (         <UserCard key={user.id} user={user} userIndex={index} />       ))}       <Button onClick={handleSubmit(onSave)}>Сохранить</Button>     </FormProvider>   ); } 
  1. В хуке useForm мы указываем значения по умолчанию, у нас только массив пользователей.

  2. Получаем данные, используя хук useFieldArray ( fields ).

  3. Запрашиваем данные с API и перерендериваем нашу форму с помощью метода reset.

  4. И, соответственно, проходим по массиву, в который записались данные с нашего API.

Давайте теперь посмотрим на код карточки.

import { TextField } from "@mui/material" import { Controller, useFormContext } from "react-hook-form" import "./App.css"  export const UserCard = (props) => {     const { user: { name, suname }, userIndex } = props     const { control } = useFormContext()      return (         <div className="card">             <div className="card__header">                 <span>Пользователь {userIndex + 1}</span>             </div>             <Controller                 name={`users[${userIndex}].name`}                 control={control}                 defaultValue={name}                 render={({ field: { value, onChange } }) => (                     <TextField                         value={value}                         onChange={onChange}                     />                 )}             />             <Controller                 name={`users[${userIndex}].suname`}                 control={control}                 defaultValue={suname}                 render={({ field: { value, onChange } }) => (                     <TextField                         value={value}                         onChange={onChange}                     />                 )}             />       </div>     ) }
  1. Обратите внимание, что в Controller теперь передается defaultValue со значением из props.

  2. Изменился также name для каждого поля. Поскольку users — это массив, мы указываем индекс элемента в квадратных скобках, а затем name и surname. Вы можете зайти в консоль и посмотреть, что происходит.

Управление списком карточек

Давайте реализуем главную задачу нашей формы — добавление и удаление пользователей из списка. Для этого воспользуемся тем же useFieldArray, который помимо fields возвращает достаточно методов, позволяющих реализовать большинство сценариев. Нам нужны только append и remove. Вот как я это реализовал.

import { useEffect } from "react" import { Button } from "@mui/material"; import { FormProvider, useFieldArray, useForm } from "react-hook-form"; import { UserCard } from "./UserCard"; import axios from "axios";  export const App = () => {   const methods = useForm({     defaultValues: {       users: []     }   })    const { control, handleSubmit, reset } = methods    const { append, remove, fields } = useFieldArray({     name: "users",     control: control   })    const onSave = (data) => {     console.log(data)   }    const onAddUser = () => {     const lastUser = fields.at(-1)     let newUserId = 1;      if (lastUser) {       newUserId = lastUser.id + 1;     }          append({       id: newUserId,       name: "",       suname: ""     })   }    const onDeleteUser = (userIndex) => {     remove(userIndex)   }    useEffect(() => {     const getUsersAsync = async () => {       const { data } = await axios.get("http://localhost:3000/users")       reset({         users: data       })     }     getUsersAsync()   }, [reset])    return (     <FormProvider {...methods}>       {fields?.map((user, index) => (         <UserCard key={index} user={user} userIndex={index} onDeleteUser={onDeleteUser} />       ))}       <Button onClick={onAddUser}>Добавить пользователя</Button>       <Button onClick={handleSubmit(onSave)}>Сохранить</Button>     </FormProvider>   ); } 

В функции добавления нам нужно получить новый id. Для этого мы получаем последний id и просто добавляем единицу. Стоит обратить внимание, что в хуке useFieldArray было добавлено поле keyName со значением key. Это сделано, потому что по умолчанию useFieldArray добавляет поле id, но так как у нас id приходит с API, а при добавлении формируется на клиенте, этот ключ следует назвать иначе, чтобы избежать конфликтов.

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

И, наконец, конечная версия UserCard.

import { Button, TextField } from "@mui/material" import { Controller, useFormContext } from "react-hook-form" import "./App.css"  export const UserCard = (props) => {     const { user: { name, suname }, userIndex, onDeleteUser } = props     const { control } = useFormContext()      return (         <div className="card">             <div className="card__header">                 <span>Пользователь {userIndex + 1}</span>                 <Button onClick={() => onDeleteUser(userIndex)}>Удалить пользователя</Button>             </div>              <Controller                 name={`users[${userIndex}].name`}                 control={control}                 defaultValue={name}                 render={({ field: { value, onChange } }) => (                     <TextField                         value={value}                         onChange={onChange}                     />                 )}             />             <Controller                 name={`users[${userIndex}].suname`}                 control={control}                 defaultValue={suname}                 render={({ field: { value, onChange } }) => (                     <TextField                         value={value}                         onChange={onChange}                     />                 )}             />       </div>     ) }

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

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

PS: Данный код был написан на JavaScript исключительно для уменьшения количества кода и упрощения чтения. Я также не стал использовать мемоизацию useCallback, поскольку это усложнило бы читаемость. Еще раз благодарю.


ссылка на оригинал статьи https://habr.com/ru/articles/746806/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *