[프론트엔드] React Hook Form의 useWatch를 활용한 비밀번호 확인 구현

Woonil·2025년 2월 18일
0

프론트엔드

목록 보기
1/2

최근에 만들어진 사이트에서는 웬만하면 회원가입 시 비밀번호를 두 번 입력하게 하고, 정상적으로 설계되었다면 두 비밀번호가 일치하는지도 확인한다. 이를 react hook form을 활용해 적절히 구현하기 위해서는 어떻게 해야 할까? 기존 프로젝트의 회원가입 양식에도 이를 적용하는 과정에서 겪었던 문제와 해결방법을 소개하고자 한다.

🤔문제

먼저 useForm register의 validate와 getValues 를 활용하여 아래 코드와 같이 구현할 수 있다.

// COMPONENT: 회원가입 비밀번호 입력 단계
import { useFormContext } from "react-hook-form";
import * as S from "./setup-step.style";
import { ISignupParams } from "../../../types/type";

export default function SetupPassword({
  onNext,
  onPrevious,
}: ISetupPasswordProps) {
  const {
    register,
    formState: { errors, isValid, touchedFields },
    getValues,
  } = useFormContext<IFieldValues>();

  return (
    <S.SetupPageWrapper>
      {/* 비밀번호 */}
      <S.Input
        id="password"
        type="password"
        placeholder="비밀번호 (12~36자)"
        {...register("password", {
          required: "비밀번호를 입력해주세요",
          /* ... */
        })}
      />
      {touchedFields.password && errors?.password && (
        <S.ErrorMessage>{errors.password.message}</S.ErrorMessage>
      )}
      {/* 비밀번호 확인 */}
      <S.Input
        id="password_confirm"
        type="password"
        /* ... */
        {...register("password_confirm", {
          required: { value: true, message: "비밀번호를 입력해주세요." },
          validate: {
            notMatched: (value) => {
              const { password } = getValues();
              return value === password || "비밀번호가 일치하지 않습니다.";
            },
          },
        })}
      />
      {errors.password_confirm && (
        <S.ErrorMessage>{errors.password_confirm.message}</S.ErrorMessage>
      )}
      /* ... */
    </S.SetupPageWrapper>
  );
}
  • 해당 방식의 문제점

    비밀번호 일치 input을 입력한 후 다시 비밀번호 input을 수정하는 경우 에러 메시지("비밀번호가 일치하지 않습니다.")가 표시되지 않는 문제가 발생하였다.

    이는 getValueswatch 와 다르게 리렌더를 트리거하거나 입력 변경 사항을 구독하지 않기 때문이다. 공식문서의 getValues 설명 가장 윗 부분에도 나와있다.

    리액트 훅 폼 공식문서 getValues

    이를 해결하기 위해서는, 유효성 검증을 지속적으로 지켜보고 동시에 리렌더링까지 수행하게 도와주는 기능이 필요해 보였다. 가지고 있던 지식으로는 마땅한 방법이 떠오르지 않아 ChatGPT의 답변을 받은 후 공식 문서를 참고했다. 결론적으로 도출한 해결책은 다음과 같다. 1) watchtrigger 을 사용하는 방법 2) useWatch 훅을 사용하는 방법

😎해결방법

useForm의 watch와 trigger로 실시간 감지

먼저, useForm의 watch와 trigger를 사용하여 password 입력을 구독하고 리액트의 useEffect 훅을 사용하여 password 변경 시마다 trigger를 동작하게 하는 방법을 시도했다.

// COMPONENT: 회원가입 비밀번호 입력 단계
import { useFormContext } from "react-hook-form";
import * as S from "./setup-step.style";
import { ISignupParams } from "../../../types/type";
import { useEffect } from "react";

type IFieldValues = Pick<ISignupParams, "password" | "password_confirm">;
export default function SetupPassword({
  onNext,
  onPrevious,
}: ISetupPasswordProps) {
  const {
    register,
    formState: { errors, isValid, touchedFields },
    getValues,
    trigger,
  } = useFormContext<IFieldValues>();

  // 비밀번호 필드의 값을 구독
  const password = watch("password");
  useEffect(() => {
    // 비밀번호가 변경될 때마다 password_confirm 필드의 유효성 검증을 강제로 실행
    const { password_confirm } = getValues();
    if (password_confirm) {
      trigger("password_confirm");
    }
  }, [password, trigger]);

  return (
    <S.SetupPageWrapper>
      {/* 비밀번호 */}
      <S.Input
        id="password"
        type="password"
        placeholder="비밀번호 (12~36자)"
        {...register("password", {
          required: "비밀번호를 입력해주세요",
          /* ... */
        })}
      />
      {touchedFields.password && errors?.password && (
        <S.ErrorMessage>{errors.password.message}</S.ErrorMessage>
      )}
      {/* 비밀번호 확인 */}
      <S.Input
        id="password_confirm"
        type="password"
        placeholder="비밀번호 확인"
        autoComplete="off"
        autoCapitalize="off"
        {...register("password_confirm", {
          required: { value: true, message: "비밀번호를 입력해주세요." },
          validate: {
	          notMatched: (value) =>
		          value === password || "비밀번호가 일치하지 않습니다.",
          },
        })}
      />

      {touchedFields.password && errors.password_confirm && (
        <S.ErrorMessage>{errors.password_confirm.message}</S.ErrorMessage>
      )}
      /* ... */
    </S.SetupPageWrapper>
  );
}

useWatch로 실시간 감지

위의 방법도 잘 동작하지만 useEffect로 인해 렌더링이 두 번 일어나는 상황이 생겼다. react hook form에서는 watch의 대안으로 useWatch 훅을 제공한다.

Behaves similarly to the watch API, however, this will isolate re-rendering at the custom hook level and potentially result in better performance for your application.

대충 기존 watch를 훅처럼 사용하게 하고, 내부적으로 렌더링 최적화가 되어 있다고 이해하면 된다.

이제 비밀번호 input에 값을 입력해도 렌더링이 두 번 발생하지 않는다.

// COMPONENT: 회원가입 비밀번호 입력 단계
import { useFormContext, useWatch } from "react-hook-form";
import * as S from "./setup-step.style";
import { ISignupParams } from "../../../types/type";

type IFieldValues = Pick<ISignupParams, "password" | "password_confirm">;
export default function SetupPassword({
  onNext,
  onPrevious,
}: ISetupPasswordProps) {
  const {
    register,
    formState: { errors, isValid, touchedFields },
  } = useFormContext<IFieldValues>();

  const password = useWatch({ name: "password" });
  const passwordConfirm = useWatch({ name: "password_confirm" });

  return (
    <S.SetupPageWrapper>
      {/* 비밀번호 */}
      <S.Input
        id="password"
        type="password"
        placeholder="비밀번호 (12~36자)"
        {...register("password", {
          required: "비밀번호를 입력해주세요",
          /* ... */
        })}
      />
      {touchedFields.password && errors?.password && (
        <S.ErrorMessage>{errors.password.message}</S.ErrorMessage>
      )}
      {/* 비밀번호 확인 */}
      <S.Input
        id="password_confirm"
        type="password"
        /* ... */
        {...register("password_confirm", {
          required: { value: true, message: "비밀번호를 입력해주세요." },
          validate: {
            notMatched: (value) =>
              value === password || "비밀번호가 일치하지 않습니다.",
          },
        })}
      />
      {touchedFields.password &&
        passwordConfirm &&
        password !== passwordConfirm && (
          <S.ErrorMessage>비밀번호가 일치하지 않습니다.</S.ErrorMessage>
        )}
      /* ... */
    </S.SetupPageWrapper>
  );
}

혹시 더 간편한 해결방법이나 틀린 부분이 있다면 댓글로 알려주세요!

profile
프론트 개발과 클라우드 환경에 관심이 많습니다:)

0개의 댓글

관련 채용 정보