React Hook Form 의 useController hook은 Controller 를 사용하면 MUI 나 AntD 같은 외부 controlled 컴포넌트를 쉽게 제어할 수 있습니다. 해당 포스트에서는 MUI 컴포넌트를 useController 를 사용하여, form 에서 사용가능한 컴포넌트로 만들어보고, 몇가지 주의사항을 말씀드리겠습니다 (React-hook-form, MUI에 대한 기본적인 설명은 생략합니다.)
MUI의 TextField를 useController hook을 사용해 form에서 사용가능한 FormInput 컴포넌트를 만들어보겠습니다.
[예제]
- FormInput/FormInput.d.ts
import { TextFieldProps } from '@mui/material'
import { ControllerRenderProps, FieldValues, UseControllerProps } from 'react-hook-form'
export type FormInputProps = UseControllerProps & Omit<TextFieldProps, keyof ControllerRenderProps>
- useController에 주입할 UseControllerProps 의 타입과 TextFieldProps 에서 useController를 통해 컴포넌트에 주입되는 타입(ControllerRenderProps)을 제외한 타입 의 인터섹션 타입을 FormInputProps의 타입으로 선언합니다.
- ControllerRenderProps(onChange, onBlur, value, name, ref) 는 useController hook이 반환하는 field 를 통해 컴포넌트에 주입하기 때문에 제외시켜야합니다.
- FormInput/index.tsx
import { TextField } from '@mui/material'
import { useController } from 'react-hook-form'
import { FormInputProps } from './FormInput'
export default function FormInput({
name,
rules,
control,
shouldUnregister,
defaultValue,
...textFieldProps
}: FormInputProps) {
const { field } = useController({
name,
rules,
control,
shouldUnregister,
defaultValue,
})
return <TextField {...field} {...textFieldProps} />
}
- 위 코드에서 props로 전달받는 (name, rules, control, shouldUnregister, defaultValue) 가 UseControllerProps, 즉 useController에 주입해야하는 속성 값들입니다.
- 위에서 말한대로 useController가 반환하는 field(onChange, onBlur, value, name, ref) 를 TextField 컴포넌트에 주입해주면 form 에서 사용할 수 있는 컴포넌트가 됩니다.
- 나머지 props 들(코드에서 ...textFieldProps)도 TextField 에 주입해줍니다.
이렇게하면 MUI의 TextField 의 본래 기능을 가지면서, form 에서도 다룰 수 있는 FormInput 컴포넌트가 완성됐습니다.
이제 이 FormInput 컴포넌트를 사용해보겠습니다.
- App.tsx
import { Box, Button } from '@mui/material'
import FormInput from 'components/FormInput'
import React from 'react'
import { FieldValues, SubmitHandler, useForm } from 'react-hook-form'
import { container, layout, submit } from 'styles'
const defaultValues = {
formInput: 'Hello World!',
}
export default function App() {
const { control, handleSubmit } = useForm<any>({
reValidateMode: 'onBlur',
defaultValues,
})
const handleOnSubmit: SubmitHandler<FieldValues> = data => {
alert(JSON.stringify(data, null, 4))
}
return (
<Box sx={layout} component="form" onSubmit={handleSubmit(handleOnSubmit)}>
<Box sx={container}>
<FormInput control={control} name="formInput" label="input" variant="outlined" color="secondary" />
<Button sx={submit} type="submit" variant="contained" color="info">
Submit
</Button>
</Box>
</Box>
)
}
- 실행화면
문제없이 완벽하게 동작하는 코드로 볼 수도 있을 것같습니다. 하지만 몇가지 문제점을 가지고 있습니다.
문제점 1. any의 사용
//App.tsx 中
(...)
const { control, handleSubmit } = useForm<any>({
reValidateMode: 'onBlur',
defaultValues,
})
(...)
useForm hook은 제네릭으로 form의 필드(FieldValues)에 대한 타입을 받습니다. 이를 any 타입으로 지정했습니다. any 타입이 나쁜건 아니지만 Typescript의 타입체커를 무력화시키기 때문에 any 사용은 되도록이면 지양해야합니다.
만약 제네릭을 지정하지 않으면 어떨까요?
타입스크립트가 control 속성에 타입 에러를 발생시킵니다. 이유는 useHook의 프로퍼티로 주입하는 defaultValues의 타입({formInput : string})이 useForm hook의 form 필드 타입으로 주입되고 이 타입은 또한 control 의 첫번째 제네릭 타입으로 주입되는데 FormInputProps의 control 타입은 따로 지정해준 적이 없기 때문에 기본타입인 FieldValues 타입이기 때문입니다.
문제점 2. name 프로퍼티
코드에서 name 프로퍼티는 모든 string 타입을 받을 수 있습니다. form 이 제출됐을 때 각 필드에 대한 구분을 이 name 프로퍼티에 설정된 값으로 하게됩니다.
타입스크립트는 data 파라미터에 어떤 key가 있는지 알 수 없기 때문에 오타 등의 실수를 하게 된다면 찾기 힘든 버그가 될 수 있습니다.
다음 포스트에서 이 두가지 문제를 Typescript의 Generic 을 사용해 개선해보도록하겠습니다.
'FrontEnd > react' 카테고리의 다른 글
React.forwardRef with typescript generics (0) | 2022.09.20 |
---|---|
Typescript, MUI (Material UI v5), React hook form 함께 사용하기 (with Generic) (2/2) (0) | 2022.09.04 |
CRA 없이 React 프로젝트 구성하기 (0) | 2022.06.25 |
StoryBook 경로 설정하기 (0) | 2022.06.24 |
styled-components html이나 body tag에 스타일 적용하기 (0) | 2022.06.13 |