기록
(TypeScript) react-hook-form에서 form과 input을 분리하는 법
als982001
2023. 6. 6. 14:28
form과 input을 분리하고 싶었지만, 어떻게 하는지 몰라서 헤메고 있던 중, 좋은 글을 발견했다. 간단히 이야기하자면 form 안에서 input을 쓸 때는 register를 이용하면 되고, 분리하고 싶을 때는 useController를 이용하면 된다는 것 같다. 다음은 간단한 Login form이다.
// Login.tsx
import React from "react";
import styled from "styled-components";
import Logo from "../Components/Global/Logo";
import MemberInput from "../Components/Global/Inputs/MemberInput";
import { FieldValues, SubmitHandler, useForm } from "react-hook-form";
import { handleLogin } from "../utils/MemberFunctions";
import { displayCenter, displayStartA } from "../styles/displays";
const Wrapper = styled.div`
// 생략
`;
const Container = styled.form`
// 생략
`;
const Inputs = styled.div`
// 생략
`;
const Button = styled.button`
// 생략
`;
interface FormValues {
userId: string;
password: string;
}
export default function Login() {
const { handleSubmit, control } = useForm<FormValues>({
defaultValues: {
userId: "",
password: "",
},
mode: "onChange",
});
const handleStartLogin = async (data: FormValues) => {
console.log(data);
};
return (
<Wrapper>
<Container onSubmit={handleSubmit(handleStartLogin)}>
<Logo logoSize={"100px"} />
<Inputs>
<MemberInput
control={control}
name="userId"
rules={{ required: true }}
width="100%"
height="40px"
placeholder="ID를 입력하세요."
/>
<MemberInput
control={control}
name="password"
rules={{ required: true }}
width="100%"
height="40px"
placeholder="비밀번호를 입력하세요."
/>
</Inputs>
<Button>로그인</Button>
</Container>
</Wrapper>
);
}
그리고 아래는 분리한 Input이다. (위의 코드에는 MemberInput이라는 이름인 것을 확인할 수 있다.)
import React from "react";
import {
Control,
RegisterOptions,
useController,
UseControllerProps,
} from "react-hook-form";
import styled from "styled-components";
interface FormValues {
userId: string;
password: string;
}
interface StyleType {
width: string;
height: string;
placeholder: string;
}
const Input = styled.input<StyleType>`
// 생략
`;
function MemberInput(props: UseControllerProps<FormValues> & StyleType) {
const { field, fieldState } = useController(props);
return (
<Input
{...field}
width={props.width}
height={props.height}
placeholder={props.placeholder}
/>
);
}
export default MemberInput;
- 기존이라면 useForm의 register에 등록했을 name들의 타입을 Form과 Input 부분 모두 미리 지정한다. 위의 코드에서는 FormValues라는 이름으로 저장되어 있다.
- (Form) useForm의 defaultValue를 통해 각 name들의 초기값을 설정하면서 control을 구조 분해 할당으로 불러온다.
- (Form) 각 Input마다 control={control}, name, rules 등을 props로 준다.
- (Input) useController에서 field를 구조 분해 할당으로 가져온다.
- (Input) input에 field를 위의 코드처럼 작성한다.
그동안 react-hook-form을 쓰면서 useController는 몰랐기에 이를 적용하는 데 너무 오랜 시간이 걸렸었다. 그래도 성공하니까 뿌듯하다. useController처럼 여기서 처음 보는 것들이 많이 있는데, 이것들에 대해 잠시 정리를 해야 겠다. 요즘 유행하는 chatGPT의 힘을 빌려 개념 정리를 하였다.
// useController 간단한 적용 예시
const { field, fieldState } = useController({
name: 'fieldName', // 입력 요소의 이름
control: form.control, // useForm 훅에서 반환된 control 객체
rules: { required: true }, // 입력 요소에 적용할 유효성 검사 규칙
});
- useController
- 컨트롤러를 생성하여 폼의 입력 요소와 상태를 관리하는 훅
- 입력 요소와 해당 입력 요소의 상태를 쉽게 연결할 수 있음
- 폼의 입력 요소를 직접 관리할 필요 없이 useForm에서 반환된 control 객체와 연결하여 간단하게 폼 상태를 관리할 수 있으며 폼의 유효성 검사, 상태 추적, 에러 처리 등을 효율적으로 할 수 있음
- field와 fieldState는 useController에서 반환되는 객체
- field: 입력 요소와 연결된 필드 속성을 나타내는 객체. 이 객체를 입력 요소에 전달하여 react-hook-form이 제공하는 다양한 기능을 활용 가능. 예를 들어, field 객체를 input 요소의 props로 전달하면 value, onChange, onBlur 등의 이벤트 핸들러가 자동으로 설정됨
- fieldState: 입력 요소의 상태 정보를 나타내는 객체. error와 isDirty, isValid 등의 속성을 포함. 이 객체를 사용하여 입력 요소의 상태에 따라 동적인 스타일이나 에러 메시지를 표시 가능.
- UseControllerProps
- useController 함수에 전달되는 매개변수의 타입을 나타내는 인터페이스. 이 인터페이스는 name, control, rules 등의 필드를 정의하며, 입력 요소의 타입과 필수 여부 등을 지정할 수 있음.
- 위의 예시 코드에서 MemberInput 컴포넌트에서 UseControllerProps를 사용하여 입력 요소의 타입과 폼 관련 설정을 전달하고, useController를 사용하여 입력 요소와 상태를 관리하고 있음.
참고한 글 (React-hook-form - useController)