사용자가 성별, 거주 지역, 닉네임, 프로필 이미지를 입력하고 다음 단계(약관 동의)로 넘어가는 폼 페이지입니다.
UX를 고려해 필수 항목(성별, 지역, 닉네임) 입력 시에만 버튼이 활성화되도록 했습니다.
프로필 이미지는 선택 사항으로 관리했습니다.
| 후보 | 장단점 | 최종 선택 |
|---|---|---|
| React Hook Form 사용 | ✅ 폼 상태 통합 관리 가능 ✅ Validation(검증) 편리 ✅ 성능 최적화 (re-render 최소화) ✅ 커스텀 입력 컴포넌트 적용 쉬움 | ⭕ |
| useState로 직접 관리 | ❌ 필드마다 상태 변수 관리 → 코드 복잡 ❌ 중복 로직 증가 | ❌ |
| Formik + Yup 조합 | ✅ 고급 검증 가능 ❌ 비교적 셋업 복잡 (Next.js App Router 초기 프로젝트에는 과함) | ❌ |
👉 결론: 프로젝트 규모, 요구사항(필수 입력 검증) 기준 React Hook Form이 가장 효율적.
FormProvider를 사용해 Context 기반으로 form 상태를 하위 컴포넌트에 전달
watch로 gender, region, nickname 값을 모니터링해, 모두 입력된 경우만 '다음' 버튼 활성화
handleSubmit으로 form 제출시 데이터 수집
onSubmit에서는 현재는 console.log로 출력하고, 추후 서버로 전송 예정
프로필 이미지는 업로드 선택사항 → 별도 validation 없이 관리
"use client";
import { FormProvider, useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
export default function SignupPage() {
const router = useRouter();
const methods = useForm<SignupFormValues>({ mode: "onChange" });
const { handleSubmit, watch, formState: { isValid } } = methods;
const gender = watch("gender");
const region = watch("region");
const nickname = watch("nickname");
const isAllRequiredFilled = gender && region && nickname;
const onSubmit = (data: SignupFormValues) => {
console.log("회원가입 데이터:", data);
router.push("/agreement");
};
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)}>
{/* 입력 컴포넌트들 */}
<button type="submit" disabled={!isValid || !isAllRequiredFilled}>
다음
</button>
</form>
</FormProvider>
);
}
🔵 포인트:
isValid+watch결과를 함께 체크하여 form 전체 완성 여부를 즉시 반영하는 로직을 구성.
React Hook Form을 제대로 활용하면 별도 상태 관리 없이 매우 깔끔한 폼 로직이 가능하다는 걸 체감함.
특히 FormProvider 패턴은 복잡한 폼 컴포넌트를 분리할 때 유지보수성이 뛰어나다는 것을 경험.
| 방법 | 장점 |
|---|---|
| FormProvider + useFormContext 사용 | ✅ props 없이 폼 상태 접근 가능 ✅ 컴포넌트 재사용성 높음 ✅ 유지보수 쉬움 |
FormProvider로 감싸서,
FormInput내부에서는 useFormContext만 호출하면register,errors를 사용할 수 있다.
회원가입 직후, 사용자가 필수 약관(3개)에 모두 동의해야만 다음 단계로 이동할 수 있는 페이지
각각의 약관을 '보기' 버튼으로 열어 자세히 확인 가능
전체 동의, 개별 동의 모두 지원
| 후보 | 장단점 | 최종 선택 |
|---|---|---|
| 커스텀 체크박스 + 팝업 설계 | ✅ 디자인 자유도 높음 ✅ 약관 보기 팝업 커스텀 가능 ✅ UX 맞춤형 제어 가능 | ⭕ |
기본 <input type="checkbox"> 사용 | ❌ 디자인 제한 ❌ 팝업 트리거 구현 불편 | ❌ |
| 외부 컴포넌트 라이브러리 사용 | ❌ 프로젝트 경량성을 해침 | ❌ |
👉 결론: 플로고의 감성(경량 + 심플 UX) 유지 위해 직접 커스텀 구현.
각 약관별 동의 여부를 agreements 객체 상태로 관리
전체 동의는 Object.values(agreements).every(Boolean)로 체크
보기 버튼 클릭 시, 해당 약관 내용을 팝업으로 띄움
팝업에서 '확인' 클릭 시 동의 처리 및 팝업 닫기
모두 동의해야 '확인' 버튼 활성화
const [agreements, setAgreements] = useState<Record<string, boolean>>({
terms: false,
privacy: false,
location: false,
});
const isAllChecked = Object.values(agreements).every(Boolean);
const handlePopupConfirm = (id: string) => {
setAgreements(prev => ({ ...prev, [id]: true }));
setSelectedPopup(null);
};
🔵 포인트: Record<string, boolean> 구조를 사용해 확장성과 코드 간결성 둘 다 확보.
약관 팝업을 단순 모달이 아니라 페이지 전환처럼 구성함으로써 모바일 UX를 부드럽게 만들 수 있었다.
상태 설계만 신경쓰면 복잡해 보이는 흐름도 boolean 조합만으로 충분히 해결 가능.