mirror of
https://github.com/ershisan99/flashcards-docs.git
synced 2025-12-17 05:09:25 +00:00
lesson 2, chapter 3: refactoring
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"chapter-1": "Глава 1. React-hook-form",
|
"chapter-1": "Глава 1. React-hook-form",
|
||||||
"chapter-2": "Глава 2. Валидация форм"
|
"chapter-2": "Глава 2. Валидация форм",
|
||||||
|
"chapter-3": "Глава 3. Рефакторинг"
|
||||||
}
|
}
|
||||||
|
|||||||
1
pages/lesson-2/chapter-3.en.mdx
Normal file
1
pages/lesson-2/chapter-3.en.mdx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Under construction
|
||||||
170
pages/lesson-2/chapter-3.ru.mdx
Normal file
170
pages/lesson-2/chapter-3.ru.mdx
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import { Callout } from 'nextra/components'
|
||||||
|
|
||||||
|
# Рефакторинг
|
||||||
|
|
||||||
|
Проблема:
|
||||||
|
|
||||||
|
- Компонент LoginForm стал слишком сложным, в нем много логики завязанной на формах, и всему виной useController.
|
||||||
|
|
||||||
|
Решение:
|
||||||
|
|
||||||
|
- Создать автономные компоненты `ControlledCheckbox` и `ControlledTextField` и использовать их в `LoginForm`
|
||||||
|
|
||||||
|
## ControlledCheckbox
|
||||||
|
|
||||||
|
### Минимально рабочая реализация
|
||||||
|
|
||||||
|
Создадим файл `controlled-checkbox.tsx` и вынесем туда логику `useController` и `Checkbox`:
|
||||||
|
|
||||||
|
```tsx filename="src/components/ui/controlled/controlled-checkbox/controlled-checkbox.tsx"
|
||||||
|
import { useController, UseControllerProps } from 'react-hook-form'
|
||||||
|
|
||||||
|
import { Checkbox, CheckboxProps } from '@/components'
|
||||||
|
|
||||||
|
export type ControlledCheckboxProps = UseControllerProps<any> &
|
||||||
|
Omit<CheckboxProps, 'onChange' | 'value' | 'id'>
|
||||||
|
|
||||||
|
export const ControlledCheckbox = ({
|
||||||
|
name,
|
||||||
|
rules,
|
||||||
|
shouldUnregister,
|
||||||
|
control,
|
||||||
|
defaultValue,
|
||||||
|
...checkboxProps
|
||||||
|
}: ControlledCheckboxProps) => {
|
||||||
|
const {
|
||||||
|
field: { onChange, value },
|
||||||
|
} = useController({
|
||||||
|
name,
|
||||||
|
rules,
|
||||||
|
shouldUnregister,
|
||||||
|
control,
|
||||||
|
defaultValue,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
{...{
|
||||||
|
onChange,
|
||||||
|
checked: value,
|
||||||
|
id: name,
|
||||||
|
...checkboxProps,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout>В UseControllerProps пока что передадим any</Callout>
|
||||||
|
|
||||||
|
Уберем из `LoginForm` логику `useController` и заменим на `ControlledCheckbox`:
|
||||||
|
|
||||||
|
```tsx filename="src/components/auth/login-form/login-form.tsx" showLineNumbers {12}
|
||||||
|
import { ControlledCheckbox } from '@/components'
|
||||||
|
|
||||||
|
// ...
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<TextField {...register('email')} label={'email'} errorMessage={errors.email?.message} />
|
||||||
|
<TextField
|
||||||
|
{...register('password')}
|
||||||
|
label={'password'}
|
||||||
|
errorMessage={errors.password?.message}
|
||||||
|
/>
|
||||||
|
<ControlledCheckbox label={'remember me'} control={control} name={'rememberMe'} />
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Проп `name` отвечает за название поля в форме, а `control` за то, какой контроллер будет использоваться.
|
||||||
|
|
||||||
|
<Callout>Убедитесь что все работает как надо</Callout>
|
||||||
|
|
||||||
|
### Типизация
|
||||||
|
|
||||||
|
Проблема:
|
||||||
|
|
||||||
|
Сейчас в качестве control и name можно передать что угодно, а это может привести к ошибкам которые тяжело дебажить.
|
||||||
|
|
||||||
|
Теперь давайте типизируем `ControlledCheckboxProps`, для этого воспользуемся дженериками:
|
||||||
|
|
||||||
|
```tsx filename="src/components/ui/controlled/controlled-checkbox/controlled-checkbox.tsx" showLineNumbers {1,5,6,8,15}
|
||||||
|
import { FieldValues, useController, UseControllerProps } from 'react-hook-form'
|
||||||
|
|
||||||
|
import { Checkbox, CheckboxProps } from '@/components'
|
||||||
|
|
||||||
|
export type ControlledCheckboxProps<TFieldValues extends FieldValues> =
|
||||||
|
UseControllerProps<TFieldValues> & Omit<CheckboxProps, 'onChange' | 'value' | 'id'>
|
||||||
|
|
||||||
|
export const ControlledCheckbox = <TFieldValues extends FieldValues>({
|
||||||
|
name,
|
||||||
|
rules,
|
||||||
|
shouldUnregister,
|
||||||
|
control,
|
||||||
|
defaultValue,
|
||||||
|
...checkboxProps
|
||||||
|
}: ControlledCheckboxProps<TFieldValues>) => {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Попробуем допустить ошибку в `name`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Все работает как и ожидалось :)
|
||||||
|
|
||||||
|
## React-hook-form devtools
|
||||||
|
|
||||||
|
Проблема:
|
||||||
|
|
||||||
|
- Дебажить через консоль неудобно, хочется видеть что происходит в форме в реальном времени.
|
||||||
|
|
||||||
|
Решение:
|
||||||
|
|
||||||
|
- Использовать [react-hook-form devtools](https://www.react-hook-form.com/dev-tools/)
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
|
||||||
|
Установим devtools:
|
||||||
|
|
||||||
|
```bash filename="Terminal"
|
||||||
|
pnpm i -D @hookform/devtools
|
||||||
|
```
|
||||||
|
|
||||||
|
### Использование
|
||||||
|
|
||||||
|
И добавим их в `LoginForm`:
|
||||||
|
|
||||||
|
```tsx filename="src/components/auth/login-form/login-form.tsx" showLineNumbers {1,7}
|
||||||
|
import { DevTool } from '@hookform/devtools'
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<DevTool control={control} />
|
||||||
|
<TextField {...register('email')} label={'email'} errorMessage={errors.email?.message} />
|
||||||
|
<TextField
|
||||||
|
{...register('password')}
|
||||||
|
label={'password'}
|
||||||
|
errorMessage={errors.password?.message}
|
||||||
|
/>
|
||||||
|
<ControlledCheckbox label={'remember me'} control={control} name={'rememberMe'} />
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Скриншоты
|
||||||
|
|
||||||
|
Теперь у нас появилась кнопка, при нажатии на которую открывается панель с информацией о форме:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Callout type={'info'}>
|
||||||
|
Эта панель будет доступна только в разработке, в опубликованном проекте кнопки не будет.
|
||||||
|
</Callout>
|
||||||
BIN
pages/lesson-2/images/controlled-checkbox-typescript-working.png
Normal file
BIN
pages/lesson-2/images/controlled-checkbox-typescript-working.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 KiB |
BIN
pages/lesson-2/images/rhf-devtool-button.png
Normal file
BIN
pages/lesson-2/images/rhf-devtool-button.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
BIN
pages/lesson-2/images/rhf-devtool-panel.png
Normal file
BIN
pages/lesson-2/images/rhf-devtool-panel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
Reference in New Issue
Block a user