구글 폼 클론 코딩을 하며 다음 기능을 구현해야했다.
필수 질문에 답변을 하지 않은 경우 경고 표시하기

그리고 최대한 다음 규칙을 지키려고 했다.
react-hook-form 을 사용하고 있으므로 라이브러리에서 제공해주는 상태를 사용하기react-hook-form 에서 에러 상태를 받아오기 위해 찾아본 결과 두가지 방법이 있었다.
const { formState, getFieldState } = useForm();
formStategetFieldStatename 파라미터를 지정해 특정 input 의 에러 값을 가져올 수 있다.// form 의 전체 에러를 가져오고 싶을 때
const { errors } = formState;
// 특정 input 태그의 에러를 가져오고 싶을 때
const { error } = getFieldState(inputName);
나는 특정 input 태그가 가진 에러를 각 카드에서 자체적으로 판별하길 원해서 두번째 방법을 사용했다.
🤔 그런데 처음엔 필수 질문에 응답을 하지 않아도 아무런 에러도 뜨지 않았다.
console.log(error); // undefined
무엇이 문제인지 살펴보았더니 내가 원하는 유효성 검사를 실시하는 기준과 기본 유효성 검사 기준이 달랐다.
useForm 에 mode 프로퍼티를 지정하면 유효성 검사를 언제 실행할지 알려준다.
const methods = useForm(); // 아무값도 주지 않은 경우 submit 시 유효성 검사
나는 각 카드에서 포커스 아웃이 된 경우, 그리고 폼을 제출하려는 경우, 이렇게 2가지 경우에 유효성 검사를 하고 싶어서 mode 프로퍼티를 onBlur 로 설정했다.
const methods = useForm({ mode: 'onBlur' });
이렇게 설정하면useForm 에 등록해준 input 요소에서 포커스 아웃이 될 때 유효성 검사가 이뤄진다.
그리고 에러 메세지를 띄울 조건을 각 컴포넌트마다 설정했다.
// Controller 를 사용하는 경우
import { Controller } from 'react-hook-form';
const Form = (id) => {
const { control } = useForm();
return (
<Controller
name={id}
rules={{
required: '필수 입력 값입니다.'
}}
control={control}
render={() => <Input />}
/>
)
}
// register 를 사용하는 경우
import { Controller } from 'react-hook-form';
const Form = (id) => {
const { register } = useForm();
return (
<input {...register(id, {
required: '필수 입력 값입니다.'
})} />
)
}
required 외에도 validate 등의 프로퍼티를 사용해서 각 입력에 맞는 유효성 검사를 해줄 수 있다.
위와 같이 에러가 발생하는 기준을 작성했다면 onBlur 메서드를 전달해줘야 한다.
useForm 에서 mode 프로퍼티를 onBlur 로 설정했으므로, 라이브러리에서 제공해주는 onBlur 이벤트를 input 에 전달시켜야만 제대로 동작한다.
register 로 등록한 input 은 반환값에 onBlur 가 포함되어 있으므로 Controller 로 등록한 경우만 onBlur 메서드를 전달해준다.
⚠️ 참고로 나는 Chakra UI 를 사용해서 순수한 Input 이 아닌 경우 (Radio, Checkbox 등) onChange 프로퍼티에 onBlur 를 적용했다.
사용하는 UI 프레임워크가 어떻게 동작하는지에 따라 onBlur 이벤트를 적절한 곳에 적용해줘야 한다.
return (
<Controller
control={control}
name={id}
rules={{
required: required && '필수 입력 값입니다.'
}}
render={({ field: { onChange, value, onBlur } }) => (
<TextField
value={value}
onChange={onChange}
onBlur={onBlur}
/>
)}
/>
)
여기까지 설정해주면 포커스 아웃 시 유효성 검사를 하는 것을 확인할 수 있다.

마지막으로 폼 제출 시에도 유효성 검사를 해야한다. 처음 응답 페이지로 들어오면 아무런 에러도 없는 상태이기 때문이다.

이 상태에선 error 객체를 조회해봐도 아무런 값이 들어있지 않다. onBlur 이벤트가 발생할 때만 유효성 검사를 하도록 설정했기 때문이다.
const { errors } = formState;
const { error } = getFieldState(id);
console.log(errors); // {}
console.log(error); // undefined
onBlur 와 onSubmit 둘 다의 모드를 한꺼번에 설정할 수 있으면 좋았겠지만... 아쉽게도 멀티 모드(?)는 존재하지 않아서 submit 이벤트 시에는 직접 에러를 트리거 해줘야 한다.
const { trigger } = useForm({ mode: 'onBlur' });
const handleSubmitForm = () => {
trigger();
}
trigger 는 파라미터로 useForm 에 등록시킨 input 의 name 프로퍼티를 받아 직접 유효성 검사를 트리거하는 메서드다.
Promise 를 반환하기 때문에 올바른 동작을 위해선 비동기처리를 해주는 것을 잊지 말자.
// 전체를 다 트리거하고 싶은 경우
trigger();
// 하나만 트리거하고 싶은 경우
trigger(inputName);
// 여러개를 트리거하고 싶은 경우
trigger([inputName1, inputName2]);
구글 폼은 제출 시 가장 상단에 있는 하나의 카드만을 트리거 시키고 + 포커싱까지 한다.
에러가 발생하는 경우 trigger 의 shouldFocus 프로퍼티로 에러가 발생하는 input 을 포커싱해줄 수 있다.
그런데 내 경우엔... 설계를 잘못해서 외부 UI 프레임워크를 react-hook-form 이 인식하지 못한건지 포커싱이 제대로 되지 않아서 임의로 포커스를 처리했다.
for (const id of cardId) {
if ((await trigger(id)) === false) {
const targetCard = document.getElementById(id) as HTMLElement;
targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
}
이렇게 처리하여 제출 시에도 유효성 검사를 한 뒤 에러가 있는 카드로 포커싱을 해주는 기능을 구현했다.
