이전부터 입력폼이 많으면 react-hook-form을 사용하는 것이 개발 경험과 성능에서 효율적이라 판단하여 꾸준히 사용해왔으나, 말 그대로 useForm의 폼 컨트롤과 유효성 검사, 에러처리만 이용했다.
그러나 이번에 input과 Label을 연결한 컴포넌트로 UX를 개선하며 생각대로 react-hook-form 라이브러리를 잘 다루지 못하게 되자, 한번 깊게 다룰 줄 알아야겠다는 다짐을 했다.
정말 그때는 공식 문서도 계속 보면서 다시 코딩하고, 안되서 팀장님께 부탁드려서 같이 의논도 하고, 그래도 안되서 StackOverflow에 질문까지 올렸었다.... 답변은 안달렸지만.
// InputLabel.tsx 컴포넌트 코드
const InputLabel = forwardRef<HTMLInputElement, InputLabelProps>(
({ className, type, label, ...props }, ref) => {
return (
<div className="relative pt-2">
<input
type={type}
className={cn(
"peer",
className,
)}
ref={ref}
{...props}
/>
{label && (
<label
htmlFor={props.id}
className={cn("peer-focus:scale-75 peer-valid:scale-75")}>
{label}
</label>
)}
</div>
);
},
);
export default InputLabel;
// Login.tsx 컴포넌트 사용시
<InputLabel
id="password"
type="password"
label="Password"
required
value={password}
onChange={(e: any) => setPassword(e.target.value)}
>
그냥 해당 컴포넌트에 쉽게 watch만 넣어서 동적으로 적용할 수 있을거라 생각한건 오산이었다.
1. watch로 input값을 체크하려면 부모 컴포넌트에서 상태가 저장되어 prop으로 직접 내려주어야 한다. 그래서 watch의 명시적인 prop 전달이 강제된다.
2. 마찬가지로 register함수 또한 부모 컴포넌트에서 상태가 저장되어 prop으로 직접 내려주어야 한다. 기존의 {...register} 형식 대신 register={register}의 형태로 내려야 watch의 조건을 체크할 수 있음을 확인했다.
// InputLabel.tsx 컴포넌트 코드
const InputLabel = forwardRef<HTMLInputElement, InputRHF>(
({ className, type, label, id, watch, ...props }, ref) => {
const fieldValue = watch(id);
const [isFocused, setIsFocused] = useState(false);
const isPeer = fieldValue?.length > 0 || isFocused;
return (
<div className="relative pt-2">
<input
type={type}
id={id}
ref={ref}
{...register}
className={cn(
"...",
className,
)}
{...props}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
{label && (
<label
htmlFor={id}
className={cn("peer-focus:scale-75 peer-valid:scale-75")}>
{label}
</label>
)}
</div>
);
},
);
export default InputLabel;
// Login.tsx 컴포넌트 사용시
<InputLabel
id="password"
type="password"
label="Password"
value={password}
watch={watch}
{...register('password', {
required: '비밀번호를 입력해주세요!',
minLength: {
value: 4,
message: '최소 6글자를 입력해주세요',
},
})}
>