[ React ]- usehookform을 이용해 로그인 구현하기

슬로그·2023년 6월 20일
0

React

목록 보기
6/12

프로젝트에서 로그인 폼을 구현해야했다.
유효성이나 많은 useState 등 때문에 까다롭게 느껴졌는데 useHookForm 라이브러리를 이용해보기로 했다.

usehookform 공식 홈페이지 에 들어가게 되면 작성법이 그래도 친절히 나와있는듯 하다.

처음해보는 프로젝트이기도 했지만 공식홈페이지에서 정보를 얻기엔 어려웠고 usehookform에 대한 구글링 결과가 많지않아서 러닝커브가 굉장했다😥..

다른로직에서도 많이 이용했지만 로그인 구현을 위한 usehookform 을 작성한걸 기록하고, usehookform 안에 watch함수를 이용해서 가져온 input value로 submit까지 해보는 방법을 회고하는겸 적어보려고 한다.

💎 EmailInput.jsx

1)

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

먼저 각각 컴포넌트를 분리해야했기 때문에 useForm 이아니라 useFormContext를 import 해준다.
이유는 공통적인 부분을 빼보자 해서 EmailInput과 PasswordInput 컴포넌트를 따로 만들고 전체 Login컴포넌트에 넣어야했기 때문이다.(후에는 Controller사용했다.)

2)

const {
    register,
    formState: { errors },
  } = useFormContext({
    defaultVlaues: {
      email: "",
    }
  });

기본적인 register함수와 유효성검사에 맞지 않을경우 error를 나타내기 위해 errors도 꺼내와줘야한다.
기본값 defaultValues는 빈값으로 작성해준다.

3)

  <Styled.Input
    {...register("email", {
      required: {
        value: true,
        message: "-이메일을 입력해주세요.",
      },
      pattern: {
        value: /^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/i,
        message: "-이메일 형식이 올바르지 않습니다. 사용가능한 이메일 주소를 입력해 주세요.",
      },
    })}
  placeholder={placeholder}
  type="email"
  id="email"
  top={top}
  purple={false}
  />

input은 통일된 공통 컴포넌트이기에 여러곳에서 쓰일거라 styled-component로 스타일링한 Input컴포넌트를 하나 만들어주고

value처럼 이용할 이름을 지정해줘야한다. email를 입력할 input이기에 "email"로 작성하였다.

value: true로 작성하면 필드가 반드시 값을 가져야 함을 의미 하는데 onSubmit했을경우 내가 작성한 message가 나타나게된다.

pattern은 지정한 이메일 패턴을 지정해주기 위해 value에 입력하고
작성한 형식이 올바르지 않을때 나타낼 message도 함께 작성해주면 된다.

📝EmailInput.jsx 전체 코드

import Input from "./Input";
import { useForm } from "react-hook-form";
import * as Styled from "../Commonstyle";
import { useFormContext } from "react-hook-form";
import { purple } from '@mui/material/colors';

export const EmailInput = ({ placeholder, label, labelText, top }) => {
  const {
    register,
    watch,
    formState: { errors },
  } = useFormContext({
    defaultVlaues: {
      email: "",
    }
  });

  return (
    <>
      <Styled.InputWrap>
        <Styled.Label>
          <Styled.LabelSpan>{label} </Styled.LabelSpan>
          {labelText}
        </Styled.Label>
        <Styled.Input
          {...register("email", {
            required: {
              value: true,
              message: "-이메일을 입력해주세요.",
            },
            pattern: {
              value: /^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/i,
              message: "-이메일 형식이 올바르지 않습니다. 사용 가능한 이메일 주소를 입력해 주세요.",
            },
          })}
          placeholder={placeholder}
          type="email"
          id="email"
          top={top}
          purple={false}
        />
      </Styled.InputWrap>
      <Styled.RequiredMessage color="rgba(146, 146, 148, 1)">
        {errors?.email?.message}
      </Styled.RequiredMessage>
    </>
  );
};

export default EmailInput;

💎 PasswordInput.jsx

EmailInput컴포넌트와 비슷한데

1) 먼저 useFormContext에서 필요한 함수들을 꺼내준다.

	 const {
        register,
        watch,
        formState: { errors },
      } = useFormContext({
        defaultVlaues: {
          password: "",
        },
      });

2)
공통 Input컴포넌트에 이번엔 이름을 password로 지정해주고 들어가야하는 pattern들과 이번엔 최대로 입력해야하는 비밀번호값을 지정해준다.

추가된 mode:"onChange"는 사용자가 비밀번호 필드에 입력하는 동안 실시간으로 비밀번호의 유효성을 검사하고 오류 메시지를 표시하기 위함이다.
사용자가 비밀번호를 입력하는 동안, 올바른 형식이 아니면 에러메시지가
표시된다.

	 <Styled.Input
          {...register("password", {
            required: {
              value: true,
              message: "-비밀번호를 입력해주세요.",
            },
            pattern: {
               value:
                 /^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$/i,
               message:
                 "-비밀번호는 숫자,영문자,특수문자를 조합해서 사용해 주세요.",
             },
             minLength: {
               value: 8,
               message: "비밀번호는 8글자 이상으로 입력해주세요.",
             },
            mode: "onChange",
          })}
          type="password"
          id="password"
          top={top}
          placeholder={placeholder}
        />

📝 PasswordInput 전체 코드

	import Input from "./Input";
import * as Styled from "../Commonstyle";
import { useFormContext } from "react-hook-form";
import React from "react";

export const PasswordInput = ({ label, placeholder, labelText, top }) => {
  const {
    register,
    watch,
    formState: { errors },
  } = useFormContext({
    defaultVlaues: {
      password: "",
      passwordConfirm: "",
    },
  });
  // console.log("에러", errors);

  return (
    <>
      <Styled.InputWrap>
        <Styled.Label>
          <Styled.LabelSpan>{label} </Styled.LabelSpan>
          {labelText}
        </Styled.Label>
        <Styled.Input
          {...register("password", {
            required: {
              value: true,
              message: "-비밀번호를 입력해주세요.",
            },
             pattern: {
               value:
                 /^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$/i,
               message:
                 "-비밀번호는 숫자,영문자,특수문자를 조합해서 사용해 주세요.",
             },
             minLength: {
               value: 8,
               message: "비밀번호는 8글자 이상으로 입력해주세요.",
             },
            mode: "onChange",
          })}
          type="password"
          id="password"
          top={top}
          placeholder={placeholder}
        />
      </Styled.InputWrap>

      <Styled.RequiredMessage color="rgba(146, 146, 148, 1)">
        {errors?.password?.message}
      </Styled.RequiredMessage>
    </>
  );
};
export const PasswordConfirm = ({ label, placeholder }) => {
  const {
    register,
    watch,
    getValues,
    formState: { errors },
  } = useFormContext({
    defaultVlaues: {
      passwordConfirm: "",
    },
  });
  return (
    <>
      <Styled.InputWrap>
        <Styled.Label>
          <Styled.LabelSpan>*</Styled.LabelSpan>
          {label}
        </Styled.Label>
        <Styled.Input
          {...register("passwordConfirm", {
            required: {
              value: true,
              message: "-비밀번호를 재입력해주세요.",
            },
            validate: {
              check: (value) =>
                value === getValues("password") ||
                "-비밀번호가 일치하지 않습니다.",
            },
          })}
          placeholder={placeholder}
          type="password"
          id="passwordConfirm"
          name="passwordConfirm"
        />
      </Styled.InputWrap>
      <Styled.RequiredMessage >
        {errors?.passwordConfirm?.message}
      </Styled.RequiredMessage>
    </>
  );
};
export default PasswordInput;

💎 Login.jsx

이제 로그인 구현하기 위한 컴포넌트를 생성해주고 PasswordInput 컴포넌트와 EmailInput을 넣어줘야하는데

사용자가 입력한 이메일 아이디와 비밀번호를 axios post 요청시 같이 담아서 보내줘야 로그인 구현이 완료 된다.

그 방법을 usehookform watch를 이용했다.

1)
부모컴포넌트인 Login컴포넌트 안에는 전달해줄 FormProvider 과 useForm을 import해줘야한다.

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

2)
상단에는

const methods = useForm();

methods함수로 이름을 지정해 useForm을 꺼내오고

3)
전체적으로 감싸줄 form 위에는 한번 더 FormProvider로 매핑해줘야 하위 EmailInput컴포넌트와 PasswordInput컴포넌트에 전달이 가능하다.

 <FormProvider {...methods}>
     <Sryled.Form>
         <EmailInput
          placeholder="이메일을 입력해주세요"
          top="23px"
          />
     	 <PasswordInput
          placeholder="비밀번호를 입력해주세요"
          top="23px"
          />              
     </Styled.Form>
 </FormProvider>

그리고 나서 form 에 onSubmit을 전달할땐

  onSubmit={methods.handleSubmit(onSubmit)}

으로 작성해줘야한다.

4)

사용자가 입력을 다 한뒤 api로 요청하는 함수를 위에 이름 onSubmit시 이름과 동일하게 만들어 준다.

내가 요청할 api문서에서는 id 와 password로 값을 전달해야했기에 먼저 form 함수 를 만들어 준뒤, id값은 EmailInput에서 이름을 지정했던 email, password는 PasswordInput에서 지정했던 이름 password을 전달해 줘야하는데
나는 reacthookform 라이브러리에서 제공하는 기능인 watch를 이용했다.
watch는 폼 필드의 값을 감시하고 해당 값의 변경을 감지할 수 있게 해주는 함수라고 알고있으면 될 것같다.

이렇게 form 함수를 만든뒤 따로 axios 함수의 url을 만든 POST_LOGIN함수에 전달해준다.

 const onSubmit = async () => {
    const form = {
      id: methods.watch("email"),
      password: methods.watch("password"),
    };
    //console.log(form);
    try {
      const response = await POST_LOGIN(form);
      const {
        data: {
          header: { isSuccessful },
        },
      } = response;
      if (isSuccessful === true) {
        console.log(response.data);
        navigate("/main");
      }
      console.log(JSON.stringify(response?.data));
      const { accessToken, refreshToken } = response?.data?.result;

      localStorage.setItem("accessToken", accessToken);
      localStorage.setItem("refreshToken", refreshToken);

      //console.log("로그인하고나서 success", success);
    } catch (err) {
      //console.log(err.response.data);
      if (err?.response) {
        setErrMsg("사용할 수 없는 ID 입니다. 관리자에 문의 바랍니다.");
      } else if (err.response?.status === 403) {
        setErrMsg("회원정보가 일치하지 않습니다.");
      }
    }
  };

🤧 후기

reacthookform 이 정보도 많이 없고 외국인분들 뿐이라 어려운 점이 많았다.
아직 모르는 속성들이 많아서 다음엔 usehookform 속성들에 대해서 적어봐야겠다.

profile
빨리가는 유일한 방법은 제대로 가는것

0개의 댓글