요약 : useState onChange 보다 간결한 코드, 성능 최적화 용이, 유지보수 쉬움
yarn add react-hook-form
react-hook-form은 TypeScript를 기본적으로 지원하므로, yarn add react-hook-form만 설치하면 바로 사용할 수 있음
-> provider라고해서 App.tsx에서 한번에 감싸면 안되나?
안됨…
FormProvider는 각 form마다 별도로 추가해야 해.
즉, App.tsx에서 한 번만 선언하는 것이 아니라, 각각의 폼(LoginPage, SignupPage 등)에 개별적으로 적용해야 해.
각 폼은 서로 다른 useForm() 상태를 가짐
• useForm()은 reset, watch, setValue 등의 메서드를 제공하는데,
폼마다 개별적인 상태를 가져야 하기 때문에 각 폼에서 FormProvider를 따로 감싸야 해.
폼 간 데이터 충돌 방지
• 예를 들어, 로그인 폼과 회원가입 폼이 App.tsx에서 같은 FormProvider를 공유하면,
한 폼에서 입력한 데이터가 다른 폼에서도 공유될 위험이 있음.
폼 제출(handleSubmit)이 독립적으로 동작해야 함
• FormProvider는 useForm()에서 생성된 methods를 전달하는 역할이므로,
각 form 컴포넌트에서 handleSubmit을 개별적으로 적용하려면
각 폼에서 useForm()을 초기화하고, 해당 FormProvider로 감싸야 한다.
FormProvider는 App.tsx에서 한 번만 선언하는 것이 아니라,
각각의 form이 있는 컴포넌트(LoginPage, SignupPage 등)에서 개별적으로 추가해야 한다.
매번 사용처에서
const methods = useForm();
<FormProvider {...methods}>
<form></form>
</FormProvider>
이 과정을 거쳐야 했다.
->
공통컴포넌트에서 FormProvider, useForm을 포함도록 공통form태그를 만들면 반복되는 불필요하고 비효율적인 코드를 간소화할 수 있다!
매 컴포넌트에서 useForm()을 새로 불러와서 쓰면
그럼 form태그 안에 LabelInputSet 쓰도록 하는 로직 그대로 가져갈수 있지 않을까?!
-> LabelInputSet에 props로
{...register(id, { required: ${labelText}을(를) 입력해 주세요.
})}
이걸 매번 보내줘야되네…
-> 결국 결론!!
CommonForm, LabelInputSet 쓰도록 하자.
반복되는 과정 최대한 공통으로 만들어서
다른사람이 쓸때 최대한 props넘겨줄 내용이 적도록!
그리고 다른사람이 쓸때 구조를 봤을때 공통컴포넌트 쓸때 좀만 생각하면 바로 이해되도록 최대한 간단하고 직관적인 구조로!!
LabelInputSet 안에서 register 하도록 제작,
CommonForm 에서는 useForm, methods, form, FormProvider 등을 갖고 있도록 제작
참고)
CommonForm.tsx, LabelInputSet.tsx 수정사항 참고
메모 사진 참고
💬 ㅎㅎ... 이후 사용하다보니 문제가 생겨
CommonForm
공통 컴포넌트를 대대적으로 리팩토링 하게 된다.
🛠️ 문제
FormProvider, form, useForm에서 불러오는 methods를 전부 CommonForm 안에서 갖고 있다보니,
정작 CommonForm을 사용하는 컴포넌트에서 특정 methods가 필요한 경우 useForm, methods는 CommonForm 안에 있어서 methods를 불러올 수가 없어 사용할 수 가 없었다.
👍 해결
FormProvider, form은 CommonForm에 그대로 유지하되, useForm을 선언 및 methods 가져오는 위치를 CommonForm 컴포넌트를 사용하는 곳에서 불러오도록 로직을 변경했다.
이렇게 하면 CommonForm을 사용하는 컴포넌트에서도 methods 중 일부 (ex. watch, getValues, setValue 등)를 사용할 수 있게 된다!!!
인증번호를 받기 위해서는 이름, 전화번호만 먼저 form을 제출해야하는데
인증번호 입력하는 부분으로 바로 넘어가버리고,
거기서 이름, 번호, 인증번호 입력해야 콘솔에 입력값이 출력됐다.
‘인증번호 전송’과 ‘완료’ 버튼이 같은 버튼이라서 함수를 하나만 뒀더니 생긴 문제.
const handleGetVerificationCode = () => {
// '인증번호 전송' 버튼 클릭 시
if (!isCodeSent) {
setIsCodeSent((prev: boolean) => !prev);
} else {
// '완료' 버튼 클릭 시
// -> 인증실패시 로직 추가 필요
if (`/userInfo/${url}` === ROUTE_PATHS.FIND_USER_ID) {
navigate(ROUTE_PATHS.FIND_USER_ID_RESULT);
} else {
navigate(ROUTE_PATHS.CHANGE_PASSWORD);
}
}
};
함수 하나에서 인증번호 전송과 완료 두가지를 한번에 처리하고 있었다.
인증번호 요청 / 인증번호 제출 단계를 분리해서 함수를 작성한다.
submit하는 함수도 code를 받았냐 안받았냐에 따라 다른 동작을 하도록 조건문으로 분리하자.
인증번호 요청, 제출 함수를 2개로 분리하고,
CommonForm에 보낼 함수는 또 하나로 만들어서
그 안에서 조건에 따라 위 2개의 함수를 실행하도록 만들면 된다!
import { SubmitHandler, useForm } from "react-hook-form";
type Inputs = {
userId: string;
password: string;
};
const LoginPage: React.FC = () => {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm<Inputs>();
const handleSubmitFunc: SubmitHandler<Inputs> = (data) => console.log(data);
console.log(watch("userId"));
return (
<form
className={styles.testForm}
onSubmit={handleSubmit(handleSubmitFunc)}
>
<label>아이디</label>
<input
{...register("userId", { required: "이름을 입력하세요." })}
/>
{errors.userId && <p>{errors.userId.message}</p>}
<label>비밀번호</label>
<input
type="password"
{...register("password", { required: "비밀번호를 입력하세요." })}
/>
{errors.password && <p>비밀번호를 입력하세요.</p>}
<button type="submit">제출</button>
</form>
// ...