react-hook-form 으로 폼 관리&검증하기

허지예·2024년 8월 13일
0

개인 프로젝트 기록

목록 보기
12/17

react-form-hook을 사용해서 폼을 관리하고 검증 로직까지 구현해보자!

react-form-hook

React에서 폼을 쉽게 관리하고 유효성 검사를 효율적으로 처리할 수 있도록 도와주는 라이브러리

  • 폼 상태를 최소화하고, 불필요한 리렌더링을 방지하여 성능을 최적화
  • 사용하기 쉬운 API, useForm 훅을 사용하여 폼을 초기화하고, register를 통해 각 폼 필드를 간단하게 관리할 수 있다.
  • 커스텀 유효성 검사도 쉽게 추가할 수 있다.

간단 예시

import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';

// 폼 데이터의 타입을 정의합니다.
interface FormSchema {
  username: string;
  password: string;
}

function MyForm() {
  // useForm 훅을 사용하여 폼을 초기화합니다.
  const { register, handleSubmit, formState: { errors } } = useForm<FormSchema>();

  // 제출 핸들러를 정의합니다.
  const onSubmit: SubmitHandler<FormSchema> = data => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="username">Username</label>
        <input
          id="username"
          {...register("username", { required: "Username is required" })}
          placeholder="Username"
        />
        {errors.username && <p>{errors.username.message}</p>}
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input
          id="password"
          type="password"
          {...register("password", { required: "Password is required" })}
          placeholder="Password"
        />
        {errors.password && <p>{errors.password.message}</p>}
      </div>

      <button type="submit">Submit</button>
    </form>
  );
}

export default MyForm;

내가 사용하는 방법

1. use~Form() 커스텀 훅

use~Form() 커스텀 훅을 정의해서 사용한다.

  • register에 넣을 값들을 미리 넣어서, 커스텀 훅에서 반환해준다.
  • validation 로직은 따로 분리해서 작성한다.
import { useForm } from 'react-hook-form';

import { emailValidation, passwordConfirmValidation, passwordValidation } from './validate';

export interface SignupFormSchema {
  email: string;
  password: string;
  passwordConfirm: string;
}

export function useSignupForm() {
  const { register, handleSubmit, watch, formState } = useForm<SignupFormSchema>();

  return {
    register: {
      email: register('email', emailValidation),
      password: register('password', passwordValidation),
      passwordConfirm: register('passwordConfirm', passwordConfirmValidation(watch('password'))),
    },
    handleSubmit,
    watch,
    formState,
  };
}

2. validation 로직

난 아래처럼 작성했다. 오류 메세지 등등 상수는 따로 작성

import { EASY_STRINGS, VALIDATION_MESSAGES } from '../model/constants';

export const requiredValidation = {
  required: VALIDATION_MESSAGES.REQUIRED,
};

export const emailValidation = {
  ...requiredValidation,
  pattern: {
    value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    message: VALIDATION_MESSAGES.INVALID_EMAIL,
  },
};

export const passwordValidation = {
  ...requiredValidation,
  minLength: {
    value: 8,
    message: VALIDATION_MESSAGES.MIN_LENGTH(8),
  },
  validate: {
    vaildateStrength: (value: string) => {
      const matches = [/[A-Z]/.test(value), /[a-z]/.test(value), /[0-9]/.test(value), /[!@#$%^&*]/.test(value)];
      return matches.filter(Boolean).length > 3 || VALIDATION_MESSAGES.STRONG_PASSWORD;
    },
    notEasyString: (value: string) =>
      !EASY_STRINGS.some((easyString) => value.includes(easyString)) || VALIDATION_MESSAGES.EASY_PASSWORD,
  },
};

export const passwordConfirmValidation = (password: string) => ({
  ...requiredValidation,
  validate: (value: string) => value === password || VALIDATION_MESSAGES.PASSWORD_MISMATCH,
});

3. Form 컴포넌트

  • ‘use client';를 작성해서 클라이언트 컴포넌트로 만들어준다.
  • 커스텀 훅 내에서 미리 정의해둔 register들을 바로 적용해준다.
  • input은 따로 스타일링을 정의해서 Input 컴포넌트를 정의해서 사용했다.
'use client';

import { Button, Input, LinkButton, Logo, Modal } from '@/shard/ui';

import { useSignupForm } from '../lib';
import type { SignupFormSchema } from '../lib';

export interface SignupFormProps {}

function SignupForm({}: SignupFormProps) {
  const { register, handleSubmit, formState } = useSignupForm();

  const onSubmit = (data: SignupFormSchema) => {
    console.log('Form Data:', data);
    // 여기에 회원가입 로직을 추가하세요.UseFormReturn
  };

  return (
    <Modal>
      <div className='flex-center flex flex-col gap-48'>
        <Logo />
        <div className='flex-center flex flex-col gap-16'>
          <form className='flex-center flex flex-col gap-36' onSubmit={handleSubmit(onSubmit)}>
            <div className='flex-center flex flex-col gap-8'>
              <Input
                id='email'
                label='이메일'
                type='text'
                {...register.email}
                error={formState.errors.email?.message}
              />
              <Input
                id='password'
                label='비밀번호'
                type='password'
                {...register.password}
                error={formState.errors.password?.message}
              />
              <Input
                id='passwordConfirm'
                label='비밀번호 확인'
                type='password'
                {...register.passwordConfirm}
                error={formState.errors.passwordConfirm?.message}
              />
            </div>
            <Button type='submit'>회원 가입</Button>
          </form>
          <LinkButton href='/login'>로그인하기</LinkButton>
        </div>
      </div>
    </Modal>
  );
}

export default SignupForm;

4. 커스텀 Input 컴포넌트

  • 여기서 좀 해멧다..
  • register에서 넘겨 준 props를 input에 넘겨주도록 해주었는데…..
  • ref 값이 안넘어가서 input에서 값을 수정하면 form value 값이 undefined가 되버린다~~
  • 컴포넌트를 forwardRef로 선언해서 해결했다.
import cn from 'classnames';
import { InputHTMLAttributes, forwardRef } from 'react';

export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  id: string;
  label: string;
  error?: string;
}
const Input = forwardRef<HTMLInputElement, InputProps>(({ id, label, error, ...props }: InputProps, ref) => (
  <div className='flex text-13 text-neutral'>
    <label className={cn('h-30 w-100 font-semibold', { 'text-red-500': error })} htmlFor={id}>
      {label}
    </label>
    <div>
      <input
        id={id}
        ref={ref} // ref 전달
        className={cn('input input-bordered mb-4 h-30 w-250 text-14', { 'input-error': error })}
        {...props} // 나머지 props 전달
      />
      <div className={cn('text-10 text-red-500', { hidden: !error })}>{error}</div>
    </div>
  </div>
));

Input.displayName = 'Input';

export default Input;

끝!

profile
대학생에서 취준생으로 진화했다가 지금은 풀스택 개발자로 2차 진화함

0개의 댓글

관련 채용 정보