-
Notifications
You must be signed in to change notification settings - Fork 1
RHF 도입기: 온보딩 프로필 폼 구조 전환 준비
FE_juncci edited this page Apr 2, 2026
·
1 revision
기존 온보딩 프로필 폼은 useState 기반으로 상태를 관리하고 있었고,
폼이 커지면서 다음 문제가 명확해졌다.
- 필드 상태가 각각 분산 (
selectedJob,nickname, 약관 등) - 제출 시점에 수동 검증 로직 작성
- 버튼 활성화 조건과 실제 검증 로직이 이중화
👉 폼 상태 / 검증 / 제출 흐름이 하나의 시스템으로 묶여 있지 않았다
특히 설문조사 폼 확장을 고려하면 이 구조는 유지보수 비용이 급격히 증가할 가능성이 높았다.
그래서 폼을 하나의 컨텍스트로 통합 관리하기 위해 RHF를 도입했다.
이번 작업의 핵심 목표는 단순 라이브러리 도입이 아니라 다음이다.
- 폼 상태를 단일 소스로 통합
- 검증 흐름을 선언적으로 정리
- 제출 흐름의 진입점을 명확히 통제
- 기존 UI 영향 최소화
useOnboardingFormState.client.ts
- 모든 필드를
useState로 개별 관리
const [selectedJob, setSelectedJob] = useState(null);
const [selectedCareer, setSelectedCareer] = useState(null);
const [nickname, setNickname] = useState('');👉 문제
- 상태 분산
- 필드 추가 시 관리 포인트 증가
👉 폼 상태 저장소를 RHF로 이동
const { control, setValue, getValues } = useForm<OnboardingFormValues>({
defaultValues: {
selectedJob: null,
selectedCareer: null,
selectedTech: [],
nickname: '',
introduction: '',
termsAgreed: false,
privacyAgreed: false,
pledgeAgreed: false,
},
});const selectedJob = useWatch({ control, name: 'selectedJob' });
const nickname = useWatch({ control, name: 'nickname' }) ?? '';👉 의미
상태를 직접 들고 있는 게 아니라 “폼 상태를 구독”하는 구조
const setNickname = (value: string) => {
setValue('nickname', value, { shouldDirty: true, shouldValidate: true });
};👉 포인트
- dirty 상태 자동 반영
- validation 트리거 통합
const handleTechToggle = (skill: Skill) => {
const currentTech = getValues('selectedTech');
const exists = currentTech.some((item) => item.id === skill.id);
const next = exists
? currentTech.filter((item) => item.id !== skill.id)
: [...currentTech, skill];
if (!exists && next.length > 5) {
setTechLimitMessage('기술 스택은 최대 5개까지 선택할 수 있어요.');
return;
}
setSelectedTech(next);
};👉 핵심 변화
기존: 로컬 상태 기반 변경: RHF 상태를 기준으로 로직 수행
return {
selectedJob,
setSelectedJob,
nickname,
setNickname,
};👉 외부 API 유지
UI 레이어는 그대로 두고 내부 구현만 RHF로 교체
useOnboardingProfileForm.client.ts
👉 제출 전에 상위 훅에서 선 검증 수행
const handleSubmit = async () => {
const trimmedNickname = form.nickname.trim();
if (!form.selectedJob || !form.selectedCareer) {
submit.setSubmitError('직무와 경력을 선택해 주세요.');
return;
}
if (!isExpert && form.selectedTech.length === 0) {
submit.setSubmitError('기술 스택을 선택해 주세요.');
return;
}
if (!trimmedNickname) {
submit.setSubmitError('닉네임을 입력해 주세요.');
return;
}
if (!allRequiredAgreed) {
submit.setSubmitError('필수 약관에 동의해 주세요.');
return;
}
await submit.handleSubmit(nicknameLimit);
};👉
- 검증 책임을 submit 훅에서 분리
- “폼 레벨 검증”을 명확히 드러냄
👉 결과
검증 흐름의 진입점이 한 곳으로 정리됨
useOnboardingSubmit.client.ts
return { isSubmitting, submitError, setSubmitError, handleSubmit };👉 setSubmitError를 외부에 노출
이전에는
- submit 내부에서만 에러 처리 가능
변경 후:
- 상위 훅에서 선 검증 → 동일 에러 채널 사용
에러 흐름이 하나로 통합됨
- UI 에러 표시
- 서버 에러
- 사전 검증 에러
👉 전부 submitError로 수렴
- 상태: useState 분산
- 검증: submit 내부 수동 처리
- UI 조건 vs 실제 검증 분리
- 상태: RHF 단일 컨텍스트
- 검증: 상위 훅 + submit 훅 분리
- 에러: 단일 채널 (
submitError)
- 서버 로직 (닉네임, 이메일) → 별도 훅 유지
- RHF는 폼 상태 + 검증까지만 담당
👉 역할 분리 유지
setNickname()
setSelectedJob()👉 이유
리팩토링 범위를 최소화하기 위해
- useWatch
- setValue
- getValues
👉 모든 상태 흐름이 여기 기준으로 동작
폼 = 상태 + 검증 + 제출 흐름
RHF보다 더 중요한 변화는
👉 검증이 어디서 시작되는지 명확해진 것
이번 구조는
앞으로 모든 폼의 기본 패턴이 될 수 있음