
React-Hook-Form이란 간단하게 Form을 다룰 수 있는 라이브러리로, React-hook-form은 기본적으로 비제어 컴포넌트로 동작하기 때문에 불필요한 연산과 리랜더가 일어나지 않는다.
react-hook-form은 비제어 컴포넌트의 장점을 그대로 살리면서 실시간 유효성 검사, 실시간 동기화 등의 API를 제공한다.
그렇다면 이번 주제에 맞는 Controller 와 Register 를 알아보자
***React-Hook-Form 설치방법
npm i react-hook-form
React-Hook-Form 에서 사용되는 Controller 와 Register 함수 모두 폼 요소를 등록하고 상태를 관리하는데 사용하는 공통점을 가지고있다.
Controller 는 주로 외부 라이브러리와 함께 사용되고 Register 함수는 주로 직접적 으로 폼 요소를 등록할 때 사용된다.
React-Hook-Form는 위에서 말했다시피 비제어컴포넌트를 사용하는 라이브러리인 반면 MUI는 React 기반의 UI라이브러리로서 제어컴포넌트 방식을 사용한다. 이때 MUI를 같이 사용하면서 Controller를 같이 사용하면 React-Hook-Form은 해당 필드의 상태를 추적하고 필요한 경우에만 업데이트를 하여 이용할 수 있다.
Contorller
외부 라이브러리와 연동이 쉬움.
직접적으로 폼요소를 관리하는 Register 에 비해 세세한 control 힘들 수 있음.
커슨텀 컴포넌트 활용시 Controller를 통해 상태 전달 및 관리가 재사용성을 높일수 있음.
대부분의 상황에서 Controller 함수를 이용하여 프로젝트의 일관성, 확장성을 유지함.
Register
폼을 직접 등록 하여 필요한 요소 control 용이.
직접적인 등록으로 자유도가 높고 소규모 폼에서는 렌더링 성능이 우수할 수 있음.
그렇다면 Register 함수를 사용해보자.
우선 나는 input 공용컴포넌트를 따로 분리하고, Validate-message도 다른곳에서 재사용이 될것같아 분리하였다.
[input 컴포넌트]
import { StyleInput } from "../Theme/input.style";
const Input = ({
type,
placeholder,
size,
color,
name,
registerKey = "",
register,
validate = {},
titleText = "",
errors = {},
...rest
}) => {
return (
<>
{titleText && <label htmlFor={registerKey}>{titleText}</label>}
<StyleInput
{...register(registerKey, validate)}
id={registerKey}
type={type}
color={color}
placeholder={placeholder}
size={size}
{...rest}
/>
<div>
{errors[registerKey] && (
<span>{errors[registerKey].message}</span>
)}
</div>
</>
);
};
export default Input;
여기서 StyleInput은 따로 input의 스타일을 정의한후 데리고온것이다. input의 스타일을 만들어 놓고, 그 input을 불러와서 공용컴포넌트를 작성하였다.
그럼 이제 register 함수 를 본격적으로 사용해보겠다.
const {
register, // 입력을 등록하거나 요소 선택시 유효성 검사 규칙 적용
handleSubmit, // 폼 유효성 검사 통과시 데이터를 받아옴
watch,// 입력 필드값 실시간으로 관찰하고 해당필드값이 변경될때마다 그 값을 반환하는 기능.
formState: { errors, isValid }, // 폼의 현재상태를 나타내는 객체. 폼의 유효성 검사에서 발생한 모든 에러/유효성검사 포함
} = useForm({ mode: "onChange" }); //사용자가 폼의 입력필드 값을 변경할때마다 유효성검사를 실시
//onSubmit 함수
const onSubmit = (data) => {
updateForms(data);
};
....
return (
<>
<form>
<label htmlFor="email">Email</label>
<Input
type={"text"}
register={register}
registerKey="email"
size="medium"
name="email"
value={email}
color="lemon"
errors={errors}
validate={isValid}
/>
</>
)
확실이 코드 하나하나 직접 입력해줘야하는것을 알 수 있다.
Controller 사용법에는 1.컴포넌트를 사용하는 방법 과 2.custom-hook으로 useController 사용하는 방법 이 있다.
나는 간단하게 사용하기 위해서 1번의 방법을 시도했지만 보통은 2번을 더 추천한다고 한다.
import React from 'react';
import { useForm, useController } from 'react-hook-form';
import { TextField } from "@material-ui/core";
const Form = () => {
const methods = useForm();
const { register, handleSubmit, control } = method;
return (
<form onSubmit={methods.handleSubmit(onSubmit)}>
<Controller
control={control} //useForm의 control
name="lastName" // 가리킬 form의 filed 명
render={({ field: { onChange, onBlur, value, ref } }) => (
//render: filed에 의존하는 children node
<TextField
onChange={field.onChange}
value={field.value}
name={field.name}
inputRef={field.ref}
/>
)}
/>
<button type="submit">Submit</button>
</form>
);
};
const App = () => {
return (
<div>
<h1>My Form</h1>
<ParentForm />
</div>
);
};
export default App;
여기서 render 안에 value 값은 useWatch를 이용해 해당 시점의 필드 값을 가져온것이라고 한다.
*useWatch 란 ? 기본적으로 watch는 입력 필드값 실시간으로 관찰하고 해당필드값이 변경될때마다 그 값을 반환하는 기능을 하는데 이 속성은 필드값이 입력될때마다 리랜더가 되기때문에 해당 문제점을 해결하기 위해서 사용되는게 useWatch이다.
나도 사용해보지 않아서 아래에 예시 작성.
//useWatch 사용 예시
import { useForm, useWatch } from 'react-hook-form';
function MyForm() {
const { register, control, handleSubmit } = useForm();
const watchedValue = useWatch({
control,
name: 'fieldName' // 감시하려는 필드의 이름
});
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register('fieldName')} placeholder="Type something" />
<p>Watched Value: {watchedValue}</p>
<button type="submit">Submit</button>
</form>
);
}
useController는 Controller 컴포넌트와 동일한 props 및 methods를 공유한다.
import React from 'react';
import { useForm, useController } from 'react-hook-form';
function TextInput({ name, control, rules }) {
const {
field: { ref, ...inputProps },
fieldState: { invalid, error },// 필드 상태
} = useController({
name,
control,
rules,
});
return (
<div>
<input {...inputProps} ref={ref} />
{invalid && <span>{error?.message}</span>}
</div>
);
}
function MyForm() {
const { control, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextInput
name="username"
control={control}
rules={{ required: '/^\d{4}-\d{2}-\d{2}$/, minLength: 3 }}
/>
<button type="submit">Submit</button>
</form>
);
}
export default MyForm;
여기서 name은 필드 이름이고 필수값이다.
control은 useForm 을 호출하여 얻은 control 객체이다. => FormProvider 사용시 선택적 사용 가능.
rules는 register 처럼 유효성 검사 규칙을 설정한다.
이번 벨로그를 작성하면서,,
소규모 프로젝트를 진행했을때 register 만 사용해봐서 호기롭게 controller를 이용해서 폼 관련 과제를 만들어 보려고 했는데, 생각보다 뭘 놓친건지 오류도 많이 나고 시간대비 효율이 난다고 생각하지 않아서 다음번에 다시 사용해보려고 register 로 돌아왔는데 register 보다 controller 를 많이 사용한다는 사실에 놀랐다.
벨로그 작성하면서 어느정도 개념을 더 잡은것같으니 위에 예시코드를 다시 내껄로 만들어야겠다. => 수정 예정..