[React] React Hook Form 라이브러리

 

앞서 useState와 useRef의 차이점을 알아보면서, useRef는 렌더링을 발생시키지 않기 때문에 성능상의 이점을 고려해 useState 대신 useRef를 사용하는 경우가 있었다. 하지만 이를 실무에 적용할 때는 단순히 성능 이슈만으로 판단하기 보다는 해당 입력 값이 UI와 어떻게 상호작용해야 하는지를 고려해야 한다.

 

useRef는 값이 변경되더라도 리렌더링을 발생시키지 않으므로, 폼에서 많은 입력 필드를 동시에 다루거나, 자주 업데이트해야 하는 값이 있을 때 성능을 개선할 수 있다. 그러나 리렌더링이 발생하지 않는다는 점은 한편으로 UI 업데이트가 필요할 때는 단점으로 작용할 수 있다. 예를 들어, 사용자가 입력한 값에 따라 실시간으로 UI가 변화해야 하거나, 입력 필드의 유효성을 즉각적으로 반영해야 할 경우에는 useRef만으로는 적절한 처리가 어려울 수 있다.

 

useState는 입력 값이 변경될 때마다 리렌더링을 발생시키기 때문에, 입력에 따라 UI가 실시간으로 반응하는 상황에 유리하다. 예를 들어, 입력값에 따라 동적으로 텍스트 색상을 변경하거나 에러 메시지를 표시하는 등의 UI 처리가 필요한 경우, useState는 즉각적으로 상태 변화를 감지하고 렌더링에 반영할 수 있다.

 

useState와 useRef 두 Hook을 적절히 조합하여 사용할 수 있다. 예를 들어, 폼 전체에서 많은 필드가 있을 때, 실시간으로 UI에 영향을 주지 않는 필드는 useRef로 관리하고, UI 업데이트가 필요한 필드만 useState로 관리할 수 있다. 하지만 이런 방식을 사용하면 코드가 길어지고 가독성이 안좋아지거나 헷갈릴 수 있다. 이를 위해 react-hook-form처럼 useRef를 사용해 성능을 극대화하면서도 필요시 useState를 사용해 UI 반응성을 유지하는 방식을 고려할 수 있다.

 

이번 포스팅에서는 react-hook-form과 사용 방법 및 예시에 대해 포스팅해보려고 한다.

 

 

 

 

01. React Hook Form

React Hook Form은 폼의 상태를 효율적으로 관리하여, 입력값을 쉽게 추적하고 업데이트할 수 있도록 간편한 방법을 제공하는 폼 관리 라이브러리이다. 리액트의 상태 관리 방식과 달리, React Hook Form은 입력 필드의 reference를 사용하여 불필요한 리렌더링을 방지하고, 가상 DOM의 업데이트를 최소화한다. 이를 통해 많은 입력 필드가 있는 복잡한 폼에서도 높은 성능을 유지할 수 있다. 또한 내장된 유효성 검사 기능을 통해 입력 빌드의 값에 대한 검등을 쉽게 수행할 수 있다. Yup, Joi와 같은 외부 유효성 검사 라이브러리와 통합하여 복잡한 검증 로직을 구현할 수 있다.

 

React Hook Form은 직관적인 API를 제공하여 복잡한 폼 로직을 단순화한다. 기본적으로 제공하는 여러 Hook(useForm, useController, useWatch 등)을 사용하여 폼을 쉽게 생성하고 관리할 수 있다.

게다가, 커스텀 훅을 사용하여 개발자가 필요한 로직을 쉽게 작성하고 재사용할 수 있도록 지원한다. 커스텀 훅을 사용하면 폼 상태, 에러 처리, 폼 제출 등의 로직을 캡슐화 가능하다.

 

 

 

02. React Hook Form 사용한 로그인 기능 구현

1️⃣ reack-hook-form 패키지 설치

npm install react-hook-form

 

 

2️⃣ Login From에 React Hook Form 연결

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

interface LoginFormInputs {
  email: string;
  password: string;
}

const Login = () => {
  const { register, handleSubmit } = useForm<LoginFormInputs>();

  const onSubmit: SubmitHandler<LoginFormInputs> = (data) => {
    alert(JSON.stringify(data)); // 제출된 데이터를 JSON 문자열로 변환하여 경고창으로 표시
  };

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <label htmlFor='email'>이메일</label>
        <input
          id='email'
          type='email'
          placeholder='test@email.com'
          {...register('email')}
        />
        <label htmlFor='password'>비밀번호</label>
        <input
          id='password'
          type='password'
          placeholder='****************'
          {...register('password')}
        />
        <button type='submit'>로그인</button>
      </form>
    </>
  );
};

export default Login;

 

  • useForm : React Hook Form의 핵심 훅으로, 폼 인스턴스를 생성하고 폼 데이터와 메서드를 제공
  • register : 입력 필드를 React Hook Form에 등록. 입력 필드에 대한 유효성 검사 규칙, 기본값 등을 설정 가능
  • handleSubmit : 폼 제출시 실행할 함수를 정의. 유효성 검사를 수행하고, 제출할 데이터를 처리하는 로직을 작성 가능

 

 

 

3️⃣ 중복 제출 방지

로그인 폼에서 사용자가 로그인 버튼을 여러 번 클릭하면 제출 이벤트가 중복해서 발생할 수 있는 문제가 발생한다.

이를 방지하기 위해 사용자가 버튼을 클릭하면 해당 버튼을 비활성화하고, 이벤트 처리가 완료된 후에 다시 활성화하는 것이 좋다.

 

useForm() 훅이 반환하는 객체의 formState 속성은 현재 폼의 상태를 나타낸다.

이 중 isSubmitting 값을 사용하여 폼이 현재 제출 중인지 여부를 알 수 있다.

따라서 로그인 버튼의 disabled 속성에 isSubmitting 값을 설정하면, 폼 제출이 완료될 때까지 버튼이 비활성화 된다.

 

폼 제출 후 비활성화 된 로그인 버튼

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

interface LoginFormInputs {
  email: string;
  password: string;
}

const Login = () => {
  const {
    register, // 입력 필드를 등록하는 함수
    handleSubmit, // 폼 제출을 처리하는 함수
    formState: { isSubmitting }, // 현재 폼의 상태에서 isSubmitting을 추출
  } = useForm<LoginFormInputs>();

  const onSubmit: SubmitHandler<LoginFormInputs> = async (data) => {
    // 폼 제출 시 호출되는 함수
    await new Promise((r) => setTimeout(r, 1000)); // 1초 지연을 추가하여 비동기 처리 시뮬레이션
    alert(JSON.stringify(data)); // 제출된 데이터를 JSON 문자열로 변환하여 경고창으로 표시
  };

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <label htmlFor='email'>이메일</label>
        <input
          id='email'
          type='email'
          placeholder='test@email.com'
          {...register('email')}
        />
        <label htmlFor='password'>비밀번호</label>
        <input
          id='password'
          type='password'
          placeholder='****************'
          {...register('password')}
        />
        <button type='submit' disabled={isSubmitting}>
          로그인
        </button>
      </form>
    </>
  );
};

export default Login;

 

 

 

 

4️⃣ 입력값 검증, 에러 처리

React Hook Form은 입력값 검증 및 에러 처리를 간편하게 구현할 수 있는 기능을 제공한다.

이를 통해 사용자로부터 수집하는 데이터의 유효성을 확인하고, 유효하지 않은 입력에 대해 적절한 처리를 할 수 있다.

 

◼︎ 기본 입력값 검증

로그인 폼과 같은 간단한 폼에서 이메일, 비밀번호는 필수 입력 사항이다.

이를 검증하기 위해 register() 함수의 두 번째 인자로 검증 옵션을 설정할 수 있다. 주요 검증 타입은 다음과 같다.

  • required : 필수 입력 여부를 설정
  • pattern : 정규 표현식을 사용하여 특정 형식을 검증. 예를 들어, 아래와 같이 patter의 value의 이메일 정규 표현식을 설정하여 검증하고, 형식에 맞지 않을 때 메시지를 지정해 출력 가능
pattern: {
              value: /\S+@\S+\.\S+/,
              message: '이메일 형식에 맞지 않습니다.',
            },

 

  • minLength: 입력값의 최소 길이를 설정. 비밀번호의 최소 길이를 검증할 때 유용

 

 

◼︎ 오류 메시지 처리

입력값이 검증에 실패하면, React Hook Form은 formState 속성의 errors 객체에 오류 내용을 저장한다.

이를 활용하여 각 입력란 아래에 적절한 오류 메시지를 표시할 수 있다.

const Login = () => {
  const {
    register,
    handleSubmit,
    formState: { isSubmitting, isSubmitted, errors },
  } = useForm<LoginFormInputs>()

위 코드인 useForm() 훅에서 formState의 errors, isSubmitting, isSubmitted 속성을 추출하여 폼 상태를 관리하게 된다. formState 속성에 errors 객체에 오류 내용을 저장하게 되는 것이다.

 

<input
  id='email'
  type='email'
  placeholder='test@email.com'
  aria-invalid={isSubmitted ? (errors.email ? 'true' : 'false') : undefined}
  {...register('email', {
    required: '이메일은 필수 입력입니다.',
    pattern: {
      value: /\S+@\S+\.\S+/,
      message: '유효한 이메일 형식이 아닙니다.',
    },
  })}
/>
{errors.email && <small role='alert'>{errors.email.message}</small>}

예를 들어, 이메일 입력란에 유효하지 않은 값을 입력했을 경우, errors.email을 통해 해당 오류 메시지를 읽어와 사용자에게 알릴 수 있다.

각 입력란에 오류가 발생했을 때, errors 객체에서 해당 오류 메시지를 가져와 사용자에게 알려줄 수 있습니다. 위과 같이 이메일 입력란에 대한 오류 처리를 할 수 있습니다.

 

  • required: 입력란이 필수임을 설정하고, 입력이 없을 경우 사용자에게 보여줄 오류 메시지를 정의
  • pattern: 이메일 형식에 대한 정규 표현식을 사용하여 유효성을 검사하고, 형식이 맞지 않을 경우 보여줄 오류 메시지를 정의

 

  • errors.email: 이메일 입력란에 오류가 있는지 확인. 오류가 있을 경우 true로 평가
  • errors.email.message: 오류가 발생했을 때, 설정한 메시지를 읽어와 화면에 표시

 

 

웹 접근성을 위해 각 입력란에 aria-invalid 속성을 설정하여 유효성 검사 결과를 스크린 리더 사용자에게 알려줄 수 있다. 유효하지 않은 입력란에는 aria-invalid="true"가 설정되며, 유효한 입력란에는 false가 설정된다.

aria-invalid={isSubmitted ? (errors.email ? 'true' : 'false') : undefined}

 

또한, 오류 메시지를 표시하는 영역에는 role="alert" 속성을 사용하여 스크린 리더가 오류 메시지를 즉시 전달하도록 할 수 있다. 

{errors.email && <small role='alert'>{errors.email.message}</small>}

입력값 검증 및 에러 처리

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

interface LoginFormInputs {
  email: string;
  password: string;
}

const Login = () => {
  const {
    register,
    handleSubmit,
    formState: { isSubmitting, isSubmitted, errors },
  } = useForm<LoginFormInputs>();

  const onSubmit: SubmitHandler<LoginFormInputs> = async (data) => {
    await new Promise((r) => setTimeout(r, 1000)); // 1초 지연
    alert(JSON.stringify(data)); // 제출된 데이터를 JSON 문자열로 변환하여 경고창으로 표시
  };

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)}>
        <label htmlFor='email'>이메일</label>
        <input
          id='email'
          type='email'
          placeholder='test@email.com'
          aria-invalid={isSubmitted ? (errors.email ? 'true' : 'false') : undefined}
          {...register('email', {
            required: '이메일은 필수 입력입니다.',
            pattern: {
              value: /\S+@\S+\.\S+/,
              message: '이메일 형식에 맞지 않습니다.',
            },
          })}
        />
        {errors.email && <small role='alert'>{errors.email.message}</small>}
        
        <label htmlFor='password'>비밀번호</label>
        <input
          id='password'
          type='password'
          placeholder='****************'
          aria-invalid={isSubmitted ? (errors.password ? 'true' : 'false') : undefined}
          {...register('password', {
            required: '비밀번호는 필수 입력입니다.',
            minLength: {
              value: 8,
              message: '8자리 이상 비밀번호를 사용하세요.',
            },
          })}
        />
        {errors.password && <small role='alert'>{errors.password.message}</small>}
        
        <button type='submit' disabled={isSubmitting}>
          로그인
        </button>
      </form>
    </>
  );
};

export default Login;

 

 

 

 


※ References

https://www.daleseo.com/react-hook-form/

 

React Hook Form 라이브러리 사용법

Engineering Blog by Dale Seo

www.daleseo.com

 

반응형