본문으로 바로가기

[React] react-hook-form 사용 및 useForm - 1

category 공유/React, Next 2022. 4. 20. 19:21

[React] react-hook-form 사용 후 및 useForm - 1

이전 게시글에서는 react-hook-form에 대한 아주 간략한 설명react만을 이용하여 회원가입 폼을 구현해보았습니다. 이번 게시글에서는 useForm에 대해 조금 더 자세하게 알아보고, react-hook-form을 이용하여 이전 게시글에서 구현한 내용을 개선해보겠습니다.

useForm

개선하기 전 react-hook-form에서 제공하는 여러가지 hook 중 1개인 useForm에 대해 알아보고 그 중에서도 register 함수에 대해 알아볼 것입니다. 간단하게 사용 예시를 들자면 아래와 같이 작성합니다. (더 자세히 알아보는 것은 아래 링크에 걸어둔 useForm 문서를 확인해보세요.)​

◆ register

  const { register, handleSubmit } = useForm<TYPE>({
    mode: "onChange"
  });

이번 게시글에서 가장 중요한 것은 register 함수입니다. register 함수를 2가지 방법으로 출력해보았습니다.

console.log("# register", register());
console.log("# register", register("name"));

위 결과 이미지를 보면 알 수 있지만 인자 값에 아무것도 넣지 않았을 경우 name=undefined, 값이 있는 경우는 name=name(해당 값)으로 나옵니다. 그리고 그 외 onChange, onBlur, ref 가 있습니다. 이것을 input element에 담아주어야 합니다. 코드로 확인해보면 아래와 같습니다.

<input {...register("name")} />;

위 코드를 간단하게 설명하면 ... 문법을 통하여 register 함수의 결과값 name, onChange, onBlur, ref를 input element에 손쉽게 넣어준 것입니다. 그럼 이 register 함수로 무엇을 할 수 있을까요? 해당 register 함수를 이용하여 react-hook-form에게 정보를 줄 수 있습니다. 조금 더 자세히 말하면 register를 이용하여 required, maxLength, minLength, min, max, pattern, validate 등 작업을 해보겠습니다. 짧고 간단한 것으로 빠르게 알아보고 이전 게시글의 코드를 수정해보겠습니다.

1. required

필수 값을 의미하는 required 입니다. 기존 input attribute로 required를 작성하면 사용자가 임의로 조작하여 삭제할 수 있었습니다. 그러나 register를 이용하면 사용자가 임의로 삭제하지 못 합니다. (해당 내용은 앞으로 언급 X) 아래 코드를 확인해봅시다.

<input
  {...register("name", {
    required: "이름은 필수 값입니다."
  })}
/>;

2. maxLength & minLength

maxLengthminLength를 설정하는 방법도 간단합니다. 아래 코드를 확인해봅시다.

<input
  {...register("name", {
    required: "이름은 필수 값입니다.",
    minLength: {
      value: 5,
      message: "이름은 5글자 이상이어야합니다."
    },
    maxLength: {
      value: 10,
      message: "이름은 10글자 이하이어야 합니다."
    }
  })}
/>;

위 코드에 message를 넣어주었는데, 이 값은 추후 해당 조건을 만족하지 못 하였을 때 error message로 띄워줄 내용을 작성하시면 됩니다.

3. min & max

min, max 값은 숫자에 사용됩니다. 아래 코드를 확인해봅시다.

<input
  type="number"
  {...register("age", {
    required: "나이는 필수 값입니다.",
    valueAsNumber: true,
    min: {
      value: 10,
      message: "나이는 10살 이상이어야 합니다."
    },
    max: {
      value: 100,
      message: "나이는 100살 이하이어야 합니다."
    }
  })}
/>;

위 코드에 추가한 valueAsNumber: trueinput element의 value값의 type이 number를 의미합니다.

4. pattern

정규식도 사용할 수 있습니다. 만약 비밀번호를 입력하는데 영어만 가능하게 하고 싶으면 아래 코드처럼 사용하시면 됩니다.

<input
  {...register("password", {
    required: "비밀번호는 필수 값입니다.",
    pattern: {
      value: /^[a-zA-Z]*$/,
      message: "비밀번호는 영어만 가능합니다."
    }
  })}
  type="password"
  placeholder="password"
/>;

5. validate

위에 나와있는 것 말고도 개발자가 custom하게 validation을 할 수 있습니다. 만약 email을 사용하는 데 gmail만 허용하고 싶은 경우 아래와 같이 사용하면 됩니다.

<input
  {...register("email", {
    required: "email은 필수 값 입니다.",
    validate: {
      domainCheck: email =>
        email.split("@")[1] === "gmail.com" || "gmail만 가능합니다."
    }
  })}
  type="email"
  placeholder="email"
/>;

이것 외에도 async, await 함수를 사용하여 비동기 validation도 할 수 있습니다.

◆ handleSubmit

handleSubmit은 form element에 onSubmit callback으로 등록하시면 됩니다. handleSubmit은 기본적으로 e.preventDefault()를 가지고 있기 때문에 따로 작성할 필요는 없습니다. 또한 해당 함수는 인자를 받습니다. 첫번째 인자는 필수 값으로 성공했을 때 실행시킬 함수를 받고, 두번째 인자실패했을 때 실행시킬 함수를 받고 해당 함수는 필수 값은 아닙니다. 간단하게 예시로 나타내면 아래 코드와 같습니다.

<form
  onSubmit={handleSubmit(
    () => console.log("성공"),
    () => console.log("실패")
  )}
>
  // ... <input .... />
</form>;

◆ errors

handleSubmit이 실행되고 난 후 error가 발생하게 되면 해당 error 값들은 formState 객체 안의 errors에 담겨있습니다. 해당 errors 객체를 이용하여 사용자들에게 표시해주면 됩니다. errors 객체 형식은 아래와 같습니다.

errors?."내가 지정한 input name"?."내가 지정한 error message" 입니다. 코드로 예시를 들자면

errors?.email?.message 입니다. ?. 은 상황에 맞게 넣고 빼주시면 됩니다.

◆ mode

useForm hook에 인자 값으로 mode를 넘겨줍니다. 여기서 mode 값onBlur onChange onSubmit onTouched all 중 1개를 넣어주시면 됩니다. onSubmit은 submit 버튼을 누른 경우, onChange는 변경될 때마다 등 해당 값 이름 그대로 동작합니다. 이 부분은 직접 해보시면 바로 이해하실 것입니다.

react-hook-form을 이용하여 코드 개선하기

수정한 코드는 아래와 같습니다.

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

interface IUserData {
  name: string;
  age: number;
  id: string;
  password: string;
  email: string;
}

const UserRegisterRhf = () => {
  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm<IUserData>({
    mode: "onSubmit"
  });

  const onValid = (data: IUserData) => {
    console.log("# onValid", data);
  };

  const onInValid = (errors: FieldErrors) => {
    console.log("# onInValid", errors);
  };

  return (
    <form onSubmit={handleSubmit(onValid, onInValid)}>
      <input
        {...register("name", {
          required: "이름은 필수 값입니다.",
          minLength: {
            value: 5,
            message: "이름은 5글자 이상이어야합니다."
          },
          maxLength: {
            value: 10,
            message: "이름은 10글자 이하이어야 합니다."
          }
        })}
        placeholder="name"
      />
      <input
        type="number"
        {...register("age", {
          required: "나이는 필수 값입니다.",
          valueAsNumber: true,
          min: {
            value: 10,
            message: "나이는 10살 이상이어야 합니다."
          },
          max: {
            value: 100,
            message: "나이는 100살 이하이어야 합니다."
          }
        })}
        placeholder="age"
      />
      <input
        {...register("id", { required: "id는 필수 값 입니다." })}
        placeholder="id"
      />
      <input
        {...register("password", {
          required: "비밀번호는 필수 값입니다.",
          pattern: {
            value: /^[a-zA-Z]*$/,
            message: "비밀번호는 영어만 가능합니다."
          }
        })}
        type="password"
        placeholder="password"
      />
      <input
        {...register("email", {
          required: "email은 필수 값 입니다.",
          validate: {
            domainCheck: email =>
              email.split("@")[1] === "gmail.com" || "gmail만 가능합니다."
          }
        })}
        type="email"
        placeholder="email"
      />
      {errors?.email?.message && <span>{errors.email.message}</span>}

      <input type="submit" />
    </form>
  );
};

export default UserRegisterRhf;

 

useForm 설명에서 본 방식들을 이용하여 각각의 input에 validation을 걸어주었습니다. 차이점으로 바로 보이는 것은 useState가 없다는 것입니다. 그렇다는 것은 rendering이 매번 되지 않아 성능에 도움을 줄 수 있습니다. 또한 errors도 하나의 객체로 모든 input을 다룰 수 있습니다. 앞서 설명한 것들을 바탕으로 구현하였기 때문에 추가적인 설명은 하지 않겠습니다.

 

이번 게시글에서는 useForm에 대한 기초적인 내용을 알아보았습니다. 해당 게시글로 대충 이해하고 공식 문서로 궁금한 것들을 더 알아보면 웬만한 form은 구현 가능할 것입니다. 그리고 위 게시글로만 보아서는 큰 차이점이 없어보일 수 있으나 직접 코드를 구현해본다면 많이 편해진 것을 확인할 수 있을 것입니다.

 

참고자료

 

마지막

해당 내용은 틀릴 수도 있습니다. 틀린 내용이 있으면 조언 부탁드립니다.

반응형