React & Library/React-hook-form

[React / React Hook Form] Introduce Basic

yoonjong Park 2023. 1. 4.
코드를 간소화 해주는
React Form Library

개요

프론트엔드 개발자가 적지 않은 수작업을 해야 하는 부분이 뭐냐고 물어보면, Form 핸들링 작업이다.

각 Form 아이템에 맞는 수많은 validation, 입력 제한, value를 관리하는 State 등.. 신경써야할 게 많다.

이 요소들을 한 방에 해결하는 것이 React Hook Form 이다.

Toss에서도 사용하는 라이브러리이고, 노마드코더 니코도 추천하고 있다.
좀 더 조사해보니까, 대부분의 사람들이 이 라이브러리를 많이 사용하고 있었다. (걍 쓰자...아니야 폼 조작한다고 걍 개고생한번 해보길 추천..그래야 감사함을..)

 

설치

yarn add react-hook-form

사용방법

const ToDoList = () => {
  const { register, watch, handleSubmit, formState } = useForm();
  const onValid = (data: any) => {
    console.log("data", data);
  };
  console.log(watch());
  
  console.log("formState", formState.errors);

  return (
    <div>

      <form
        style={{ display: "flex", flexDirection: "column" }}
        onSubmit={handleSubmit(onValid)}
        {/* // 모든 Validation을 마친 호출한 이후에, onValid를 실행하게 됨 */}
        >
        <input
          {...register("Email", { required: true })}
          //  required 를 input tag 내에서 사용하지 않는 이유 : User의 조작을 방지하기 위함
          placeholder="Email"
        />
        <input
          {...register("FirstName", { required: true })}
          placeholder="First Name"
        />
        <input
          {...register("LastName", { required: true })}
          placeholder="Last Name"
        />
        <input
          {...register("Username", {
            required: "작성해주세요.",
            minLength: { value: 5, message: "5자 이상 적어주세요." },
          })}
          placeholder="Username"
        />
        {formState.errors.Username && <>{formState.errors.Username.message}</>}
        <input
          {...register("Password", { required: true, minLength: 5 })}
          placeholder="Password"
        />
        <button>Add</button>
      </form>
    </div>
  );
};

register

form item을 name과 함께 등록

required, minLength ...

validation type설정

watch

입력된 value 추적 (onChange value). 딱히 쓸 데는 없는 것 같다. 개발 시, console.log 대신 사용정도..?

watch로 출력되는 Form Item Value

formState

현재 form에 저장된 value들을 저장한 값.

formState 덕분에 많은 state가 사라졌다. 굉장히 좋은 기능이다.

위 코드로 formState.error 값을 출력하면 위와 같이 나온다.

사용방법 개선1

const ToDoList = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<IForm>({ defaultValues: { email: "@naver.com" } });  // 기본 값을 간단하게 설정할 수 있다.
  const onValid = (data: any) => {
    console.log("data", data);
  };

  return (
    <div>
      <form
        style={{
          display: "flex",
          flexDirection: "column",
        }}
        onSubmit={handleSubmit(onValid)}>
        <input
          {...register("email", {
            required: "입력은 필수",
            pattern: {
              value: /^[A-Za-z0-9._%+-]+@naver.com/,  // 사용된 정규식. 이 부분은 API를 통해서 체크도 가능해진다. ex) 있는 ID입니다. 같은 것.
              message: "네이버 메일만 가입이 가능합니다.",
            },
          })}
          placeholder="Email"
        />
        <>{errors?.email?.message}</>
        <input
          {...register("firstName", { required: "입력은 필수" })}
          placeholder="First Name"
        />
        <>{errors?.firstName?.message}</>
        <input
          {...register("lastName", { required: "입력은 필수" })}
          placeholder="Last Name"
        />
        <>{errors?.lastName?.message}</>
        <input
          {...register("username", {
            required: "입력은 필수",
            minLength: { value: 5, message: "5자 이상 적어주세요." },
          })}
          placeholder="Username"
        />
        <>{errors?.username?.message}</>
        <input
          {...register("password", {
            required: "입력은 필수",
            minLength: { value: 5, message: "5자 이상 적어주세요." },
          })}
          placeholder="Password"
        />
        <>{errors?.password?.message}</>
        <button>저장</button>
      </form>
    </div>
  );
};

pattern

pattern 이라는 것을 활용하면, 정규식, api 호출 결과 값 등을 활용해서 체크하는 것도 가능해진다. 이런 장점들 덕분에, 핸들링이 매우 간편해지고 유용해지는 것이 된다. 👍

렌더링 된 화면

렌더링되는 화면

 

사용방법 개선2

interface IForm {
  email: string;
  firstName: string;
  lastName: string;
  username: string;
  password: string;
  password1: string;
  extraError?: string;
}

const ToDoList = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
    setError,
  } = useForm<IForm>({ defaultValues: { email: "@naver.com" } });
  console.log("errors", errors);

  const handleValid = (data: IForm) => {
    if (data.password !== data.password1) {
      setError(
        "password1",
        { message: "password와 다릅니다." },
        { shouldFocus: true }
      );
      // 에러가 있는 input 으로 focus 해주는 점이 좋음.
    }
    // setError("extraError", { message: "Server offline" });
    console.log("errors", errors);
    console.log("data", data);
    //
  };

  return (
    <div>
      {/* // 모든 Validation을 마친 호출한 이후에, onValid를 실행하게 됨 */}
      <form
        style={{
          display: "flex",
          flexDirection: "column",
        }}
        onSubmit={handleSubmit(handleValid)}>
        <input
          {...register("email", {
            required: "입력은 필수",
            pattern: {
              value: /^[A-Za-z0-9._%+-]+@naver.com/,
              message: "네이버 메일만 가입이 가능합니다.",
            },
          })}
          //  required 를 직접 사용하지 않는 이유 : User의 조작을 방지하기 위함
          placeholder="Email"
        />
        <>{errors.email?.message}</>
        <input
          {...register("firstName", {
            required: "입력은 필수",
            validate: {
              noHacker: value =>
                value.includes("hacker") ? "해커. 잘가요" : true,
              noDicker: value =>
                value.includes("dicker") ? "고추. 잘가요" : true,
              // return string은 error message를 전달
              // 여러 개 등록하는 것도 가능
            },
          })}
          placeholder="First Name"
        />
        <>{errors.firstName?.message}</>
        <input
          {...register("lastName", { required: "입력은 필수" })}
          placeholder="Last Name"
        />
        <>{errors.lastName?.message}</>
        <input
          {...register("username", {
            required: "입력은 필수",
            minLength: { value: 5, message: "5자 이상 적어주세요." },
          })}
          placeholder="Username"
        />
        <>{errors.username?.message}</>
        <input
          {...register("password", {
            required: "입력은 필수",
            minLength: { value: 5, message: "5자 이상 적어주세요." },
          })}
          placeholder="Password"
        />
        <>{errors.password?.message}</>
        <input
          {...register("password1", {
            required: "입력은 필수",
            minLength: { value: 5, message: "5자 이상 적어주세요." },
          })}
          placeholder="Password Confirm"
        />
        <>{errors.password1?.message}</>
        <button>저장</button>
        <>{errors.extraError?.message}</>
      </form>
    </div>
  );
};

export default ToDoList;

extraError

form 전체에 대한 에러를 핸들링할 경우이다.

shouldFocus

나는 잘 안되던데, 왜 안되는지는 잘 모르겠다. 에러 났을 때 input focus를 잡아주는 기능이라고 한다.

근데, 나는 따로 설정안해줘도 자동으로 그렇게 되던데... 버전이 달라서 업데이트 되었나? 싶었다. (정확하지는 않다.)

validate

여러 가지 방법의 에러를 핸들링 하는 것이 가능하다. 매우 괜찮은 기능이다. 

 

참고

정규식을 작성하고 테스트 해볼 수 있다.

 

regex101: build, test, and debug regex

Regular expression tester with syntax highlighting, explanation, cheat sheet for PHP/PCRE, Python, GO, JavaScript, Java, C#/.NET.

regex101.com

댓글