회원가입 폼 쉽게 만들기 : 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 컴포넌트로 웹 성능 최적화하기
코샵
코샵
나의 코딩 일기장
    반응형
  • 코샵
    끄적끄적 코딩 공방
    코샵
    • 분류 전체보기 (545)
      • 상품 추천 (54)
      • MongoDB (4)
      • 하드웨어 (9)
      • 일기장 (4)
      • Unity (138)
        • Tip (41)
        • Project (1)
        • Design Pattern (8)
        • Firebase (6)
        • Asset (2)
      • 파이썬 (128)
        • Basic (41)
        • OpenCV (8)
        • Pandas (15)
        • PyQT (3)
        • SBC(Single Board Computer) (1)
        • 크롤링 (14)
        • Fast API (29)
        • Package (6)
      • Linux (4)
      • C# (97)
        • Algorithm (11)
        • Window (7)
      • TypeScript (50)
        • 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)
  • 인기 글

  • 태그

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

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

티스토리툴바