react-hook-form으로 입력 폼 만들기

Shelby Choi·2023년 1월 13일

들어가며

IN SOPT 앱잼 기간 동안 다양하고 챌린징한 기능들을 많이 구현해봤는데, 그 중에서도 사이즈 입력 폼이 예상 밖의 복병이었다.

생각보다 로직이 많이 복잡해서 구현도 그렇고 리팩토링에도 많은 시간을 쏟았는다. 사이즈 input 별로 입력해야하는 값이 정해진 범위 내에 들어왔는지 검사해주어야 했고, 만약 사용자가 상의만 입력하겠다고 했다면 하의 유효성 검사는 선택사항이 되어야 했다. 이러한 복잡한 로직을 비교적 편리하게 구현할 수 있게 도와준 라이브러리가 바로 react-hook-form 이었기에 이에 대해 소개하고자 한다.


React Hook Form 이란?

사용자가 웹에 로그인을 할 때에는 아이디/비밀번호를 폼에 입력해야한다. 하지만 사용자가 알파벳 대신 한글을 입력하지는 않았는지, 이메일에 @를 잘 입력했는지 등을 확인해주기 위해서는 유효성 검사를 통해 예외를 방지할 필요가 있다. 이러한 유효성 검사를 효율적으로 할 수 있게 도와주는 라이브러리가 바로 react-hook-form 이다.


React에서 Form을 다루는 방법

사용자가 정보를 입력할 수 있는 요소인 input 을 이용하면 입력된 데이터를 관리할 수 있다. 해당 데이터가 제출되었음을 부모요소인 form 이 감지해서, 여러가지 원하는 처리를 할 수 있을 것이다.

만약 라이브러리를 이용하지 않고 이를 처리한다면 어떻게 될까? 먼저 input 안의 value을 state로 선언한 후 input의 변경이 감지될 때마다 value 값을 업데이트 해주는 onChange 함수를 선언해야 한다. 그런 다음 제출 버튼을 클릭했을 때 이를 처리해주는 onSubmit 함수까지 작성해주어야 한다.

import React, { useState } from "react";

function SizeForm() {
  const [length, setLength] = useState("");

  const onChange = (event: React.FormEvent<HTMLInputElement>) => {
    const {
			currentTarget: { value },
		} = event;
    setLength(value);
  };

	const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
		event.preventDefault();
		console.log(length);
	};

  return (
    <form onSubmit={onSubmit}>
      <label>
        총장:
        <input
          type="number"
          name="length"
          value={length}
          onChange={onChange}
        />
      </label>
      <button type="submit">제출하기</button>
    </form>
  );
}

만약 input이 여러개라면 어떨까? value나 onChange도 일일이 전달해주어야 하고, 유효성 검사까지 해주어야 한다면 더욱 복잡해질 것이다.

물론 Custom Hook을 사용해서 보다 쉽게 구현할 수도 있다. 이번에는 유효성까지 검사하는 form을 만들어보자.

import { useEffect, useState } from "react";

function useForm({ initialValues, onSubmit, validate }) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [submitting, setSubmitting] = useState(false);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setValues({ ...values, [name]: value });
  };

  const handleSubmit = async (event) => {
    setSubmitting(true);
    event.preventDefault();
    await new Promise((r) => setTimeout(r, 1000));
    setErrors(validate(values));
  };

  useEffect(() => {
    if (submitting) {
      if (Object.keys(errors).length === 0) {
        onSubmit(values);
      }
      setSubmitting(false);
    }
  }, [errors]);

  return {
    values,
    errors,
    submitting,
    handleChange,
    handleSubmit,
  };
}

export default useForm;

우선 파라미터로 초기값과, onSubmit 함수, 유효성에 해당하는 validate를 받는다.

이와 같이 Hook을 만들어서 Form을 구현할 수도 있지만, react-hook-form에서는 편의성 뿐만 아니라 여러가지 유용한 기능도 제공하고 있다.


react-hook-form 시작하기

우선 패키지를 다음과 같이 설치해준다.

  $ npm install react-hook-form

그런 다음 패키지를 import하여 useForm 훅을 사용하면 각종 props를 제공받을 수 있는데, 이를 이용하여 다양한 기능들을 구현할 수 있다.

  const {
      register,
      unregister,
      formState,
      watch,
      handleSubmit,
      reset,
      resetField,
      setErrors,
      clearErrors,
      setValue,
      setFocus,
      getValues,
      getFieldState,
      trigger,
      control
  } = useForm({
    mode,
    reValidateMode,
    defaultValues,
    resolver,
    context,
    shouldFocusError,
    shouldUnregister,
    criteriaMode
  });

  1. register onChange와 setState를 한번에 도와주는 함수.

첫번째 파라미터로는 input의 name에 해당하는 key값을 string으로 넣고, 두번째 파라미터에는 유효성 검사 객체를 전달할 수 있다. register를 props로 전달할 때는 다음과 같이 spread 연산자를 통해 전달해주어야 한다.

// 필수로 입력해야하고, 최소 10자를 입력해야하는 Email input
<input {...register("Email", { required: true, minimumLength: 10 })} placeholder="Email"/>

또는 다음과 같이 RegExp 정규식을 전달해서 유효성 로직을 전달할 수도 있다.

<input {...register("Email", { 
    required: true, 
    pattern: { 
      value: /^[A-Za-z0-9._%+-]+@naver.com$/,
    }
  })} 
  placeholder="Email"/>
  1. formState 현재 form이 어떤 상태인지를 저장하고 있는 객체
      formState: { 
        isDirty, 
        dirtyFields, 
        touchFields, 
        defaultValues, 
        isSubmitted, 
        isSubmitSuccessful,
        isSubmitting,
        isLoading,
        submitCount,
        isValid,
        isValidating,
        erros
      }

만약 유효성 객체의 value로 boolean값이 아닌 string값을 전달했다면, 다음과 같이 아무것도 입력하지 않았을 때 에러 메시지를 출력할 수 있다.

  alert(formState.errors["password"].message}) // Password is required
  ...
  <input 
        {...register("password", {
        required: "Password is required"
        minLength: {
        value: 5,
        message: "Your password is too short"
      }
                     })} 
  placeholder="password"/>

또는 validate에 함수를 전달해 내가 만든 유효성에 따라 검사할 수도 있다.

<input {...register("name", {
   validate: {
     noNico: (value) => value.includes("Jake") ? "Not allowed" : true;
   }
 })}
 placeholder="name"
 />

  1. watch form의 입력값들의 변화를 관찰하는 함수
    useEffect와 같이 사용하면 입력값의 변화를 객체로 반환받을 수 있다.
    useEffect(() => {
      watch((formObject) => {
        console.log(formObject);
      });
    }, [watch]);

  1. handleSubmit 유효성 검사를 담당하는 함수.
    인자로 유효성 검사를 모두 통과했을 때 실행할 함수를 전달할 수 있다.

    const onValid = (data) => {
    	console.log(data);
    }
    
    <form onSubmit=handleSubmit(onValid)>
    ...

이렇듯 props들을 이용하면 다음과같이 input이 많더라도 간단하게 폼을 구현할 수 있다.

import { useForm } from "react-hook-form";

function UserForm() {
	const { register, handleSubmit, formState, setError } = useForm();

	const onValid = (data) => {
		if(data.password !== data.password1) {
			setError("password1", 
				{ message: "Password are not the same" },
			)
		}
	};

	...
	<form onSubmit={handleSubmit(onValid)}>
		<input {...register("Email")} placeholder="Email"/>
		<input {...register("password1")} placeholder="password1"/>
		<input {...register("password2")} placeholder="password2"/>
	</form>
}


레퍼런스
https://react-hook-form.com/
https://www.daleseo.com/react-hook-form/
https://www.daleseo.com/react-forms-with-hooks/

profile
React를 애정하는 FE 개발자

0개의 댓글