회원가입 폼 쉽게 만들기 : react-hook-form

2024. 11. 22. 10:56·TypeScript
반응형

소개

폼(Form) 관리는 웹 개발에서 매우 중요한 부분입니다. React에서 react-hook-form을 사용하면 복잡한 폼 상태 관리와 유효성 검사를 쉽게 구현할 수 있습니다. 이번 글에서는 회원가입 폼을 예제로 react-hook-form의 사용법을 자세히 알아보겠습니다.

react-hook-form 설치

npm install react-hook-form
// or
yarn add react-hook-form

기본 회원가입 폼 구현

import { useForm } from 'react-hook-form';
import { useState } from 'react';

// 폼 데이터 타입 정의
interface SignUpFormData {
  email: string;
  password: string;
  passwordConfirm: string;
  name: string;
  age: number;
  terms: boolean;
}

export default function SignUpForm() {
  const { 
    register, 
    handleSubmit,
    watch,
    formState: { errors }
  } = useForm<SignUpFormData>();

  const onSubmit = (data: SignUpFormData) => {
    console.log('폼 제출 데이터:', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow">
      <h2 className="text-2xl font-bold mb-6">회원가입</h2>

      {/* 이메일 입력 필드 */}
      <div className="mb-4">
        <label className="block mb-2">이메일</label>
        <input
          {...register('email', {
            required: '이메일은 필수입니다',
            pattern: {
              value: /\S+@\S+\.\S+/,
              message: '이메일 형식이 올바르지 않습니다'
            }
          })}
          className="w-full p-2 border rounded"
        />
        {errors.email && <p className="text-red-500 mt-1">{errors.email.message}</p>}
      </div>

      {/* 비밀번호 입력 필드 */}
      <div className="mb-4">
        <label className="block mb-2">비밀번호</label>
        <input
          type="password"
          {...register('password', {
            required: '비밀번호는 필수입니다',
            minLength: {
              value: 8,
              message: '비밀번호는 8자 이상이어야 합니다'
            },
            pattern: {
              value: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/,
              message: '비밀번호는 문자, 숫자, 특수문자를 포함해야 합니다'
            }
          })}
          className="w-full p-2 border rounded"
        />
        {errors.password && <p className="text-red-500 mt-1">{errors.password.message}</p>}
      </div>

      {/* 비밀번호 확인 필드 */}
      <div className="mb-4">
        <label className="block mb-2">비밀번호 확인</label>
        <input
          type="password"
          {...register('passwordConfirm', {
            required: '비밀번호 확인은 필수입니다',
            validate: (value) => 
              value === watch('password') || '비밀번호가 일치하지 않습니다'
          })}
          className="w-full p-2 border rounded"
        />
        {errors.passwordConfirm && (
          <p className="text-red-500 mt-1">{errors.passwordConfirm.message}</p>
        )}
      </div>

      {/* 이름 입력 필드 */}
      <div className="mb-4">
        <label className="block mb-2">이름</label>
        <input
          {...register('name', {
            required: '이름은 필수입니다',
            minLength: {
              value: 2,
              message: '이름은 2자 이상이어야 합니다'
            }
          })}
          className="w-full p-2 border rounded"
        />
        {errors.name && <p className="text-red-500 mt-1">{errors.name.message}</p>}
      </div>

      {/* 나이 입력 필드 */}
      <div className="mb-4">
        <label className="block mb-2">나이</label>
        <input
          type="number"
          {...register('age', {
            required: '나이는 필수입니다',
            min: {
              value: 14,
              message: '14세 이상만 가입 가능합니다'
            }
          })}
          className="w-full p-2 border rounded"
        />
        {errors.age && <p className="text-red-500 mt-1">{errors.age.message}</p>}
      </div>

      {/* 약관 동의 체크박스 */}
      <div className="mb-6">
        <label className="flex items-center">
          <input
            type="checkbox"
            {...register('terms', {
              required: '약관 동의는 필수입니다'
            })}
            className="mr-2"
          />
          <span>이용약관에 동의합니다</span>
        </label>
        {errors.terms && <p className="text-red-500 mt-1">{errors.terms.message}</p>}
      </div>

      {/* 제출 버튼 */}
      <button
        type="submit"
        className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600"
      >
        가입하기
      </button>
    </form>
  );
}

커스텀 유효성 검사 추가

const validatePassword = (password: string) => {
  const hasLower = /[a-z]/.test(password);
  const hasUpper = /[A-Z]/.test(password);
  const hasNumber = /\d/.test(password);
  const hasSpecial = /[@$!%*#?&]/.test(password);

  if (!hasLower) return '소문자를 포함해야 합니다';
  if (!hasUpper) return '대문자를 포함해야 합니다';
  if (!hasNumber) return '숫자를 포함해야 합니다';
  if (!hasSpecial) return '특수문자를 포함해야 합니다';

  return true;
};

// 비밀번호 필드에 적용
{...register('password', {
  validate: validatePassword
})}

비동기 유효성 검사 추가

// 이메일 중복 체크 함수
const checkEmailDuplicate = async (email: string) => {
  try {
    const response = await fetch(`/api/check-email?email=${email}`);
    const data = await response.json();
    return !data.exists || '이미 사용중인 이메일입니다';
  } catch (error) {
    console.error('이메일 체크 오류:', error);
    return '이메일 확인에 실패했습니다';
  }
};

// 이메일 필드에 적용
{...register('email', {
  validate: {
    duplicate: checkEmailDuplicate
  }
})}

폼 데이터 처리와 제출

const onSubmit = async (data: SignUpFormData) => {
  try {
    const response = await fetch('/api/signup', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new Error('회원가입 실패');
    }

    // 성공 처리
    alert('회원가입이 완료되었습니다!');
    // 로그인 페이지로 이동
    router.push('/login');
  } catch (error) {
    console.error('회원가입 오류:', error);
    alert('회원가입 중 오류가 발생했습니다.');
  }
};

폼 상태 관리

const {
  formState: { isSubmitting, isDirty, isValid }
} = useForm<SignUpFormData>({
  mode: 'onChange' // 실시간 유효성 검사
});

// 제출 버튼 상태 처리
<button
  type="submit"
  disabled={!isDirty || !isValid || isSubmitting}
  className={`w-full p-2 rounded
    ${!isDirty || !isValid || isSubmitting
      ? 'bg-gray-300 cursor-not-allowed'
      : 'bg-blue-500 hover:bg-blue-600'}`}
>
  {isSubmitting ? '처리중...' : '가입하기'}
</button>

에러 메시지 커스터마이징

const errorMessages = {
  email: {
    required: '이메일을 입력해주세요',
    pattern: '올바른 이메일 형식이 아닙니다'
  },
  password: {
    required: '비밀번호를 입력해주세요',
    minLength: '비밀번호는 8자 이상이어야 합니다'
  }
};

// 에러 메시지 표시 컴포넌트
const ErrorMessage = ({ error }: { error: any }) => {
  if (!error) return null;

  return (
    <p className="text-red-500 text-sm mt-1">
      {error.message}
    </p>
  );
};

결론

react-hook-form을 사용하면 복잡한 폼 관리와 유효성 검사를 쉽게 구현할 수 있습니다. 특히 타입스크립트와 함께 사용하면 더욱 안전하고 견고한 폼을 만들 수 있습니다. 실시간 유효성 검사, 커스텀 검증 로직, 비동기 검증 등 다양한 기능을 활용하여 사용자 친화적인 회원가입 폼을 구현해보세요.

저작자표시 비영리 변경금지 (새창열림)

'TypeScript' 카테고리의 다른 글

프론트엔드와 백엔드 간 Enum 동기화  (0) 2024.12.07
React 환경 변수 : .env  (1) 2024.12.05
3D 카드 플립 효과로 배우는 실전 Tailwind 활용법  (0) 2024.11.20
[Next.js 실전] Image 컴포넌트로 웹 성능 최적화하기  (0) 2024.11.18
[React 최적화] 메모이제이션 완벽 가이드 - useMemo, useCallback, React.memo 실전 활용법  (1) 2024.11.17
'TypeScript' 카테고리의 다른 글
  • 프론트엔드와 백엔드 간 Enum 동기화
  • React 환경 변수 : .env
  • 3D 카드 플립 효과로 배우는 실전 Tailwind 활용법
  • [Next.js 실전] Image 컴포넌트로 웹 성능 최적화하기
코샵
코샵
나의 코딩 일기장
    반응형
  • 코샵
    끄적끄적 코딩 공방
    코샵
    • 분류 전체보기 (727)
      • 스마트팜 (1)
      • 상품 추천 (223)
      • DataBase (0)
        • MongoDB (4)
        • PostgreSQL (0)
      • 하드웨어 (18)
      • 일기장 (4)
      • 파이썬 (130)
        • Basic (41)
        • OpenCV (8)
        • Pandas (15)
        • PyQT (3)
        • SBC(Single Board Computer) (1)
        • 크롤링 (14)
        • Fast API (29)
        • Package (6)
      • Unity (138)
        • Tip (41)
        • Project (1)
        • Design Pattern (8)
        • Firebase (6)
        • Asset (2)
      • Linux (4)
      • C# (97)
        • Algorithm (11)
        • Window (7)
      • TypeScript (51)
        • CSS (10)
      • Git (11)
      • SQL (5)
      • Flutter (10)
        • Tip (1)
      • System (1)
      • BaekJoon (6)
      • Portfolio (2)
      • MacOS (1)
      • 유틸리티 (1)
      • 서비스 (6)
      • 자동화 (3)
      • Hobby (10)
        • 물생활 (10)
        • 식집사 (0)
  • 인기 글

  • 태그

    유니티
    C#
    쇼핑몰리뷰
    programmerlife
    긴유통기한우유
    상품 리뷰 크롤링
    cv2
    ipcamera
    리뷰이관
    믈레코비타멸균우유
    unity
    appdevelopment
    리스트
    codingtips
    파이썬
    셀레니움
    스크립트 실행 순서
    list
    카페24리뷰
    리뷰관리
    devlife
    programming101
    learntocode
    Python
    라떼우유
    rtsp
    스크립트 실행
    codingcommunity
    스마트스토어리뷰
    카페24리뷰이관
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
코샵
회원가입 폼 쉽게 만들기 : react-hook-form
상단으로

티스토리툴바