useForm 사용하여 로그인 & 회원가입 구현 - 1.로그인

기존 프로젝트의 회원가입 & 로그인은 아래와 같이 global state로 유저 email, 비밀번호, 이름 등의 정보를  관리하여 한번에 signup 혹은 login API를 호출할 때, body에 json 형태로 담아 호출하는 방식으로 진행하였다. 

//ridge.ts
export const signupState = newRidgeState({
  email: '',
  password: '',
  name: '',
  username: '',
  hiddenUsername: '',
  description: '',
});

//signupPage.tsx
const SignUpPage = () => {
	const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
  	const [valid, setValid] = useState<boolean>(true);
  	const [validPassword, setValidPassword] = useState<boolean>(true);
    ............ 생략 ............
    	<Button
            tw={{ borderRadius: 'rounded-2xl' }}
            variant="outlined"
            disabled={!email || !password || !valid || !validPassword}
            onClick={async () => {
                await signupState.set((prev) => ({
                  ...prev,
                  email,
                  password,
               }));
          >
            Sign up
          </Button>
}

global state로 관리하는 방식의 경우, 각 페이지 마다 값을 저장하고 있는 state들이 많아서 코드의 유지보수 및 가시성이 떨어지는 단점이 생기는데 React-Hook-Form의 useForm을 사용하면, 이러한 문제를 해결할 수 있다.

1. FormValues의 구성 요소 타입 지정하기

아래와 같이 로그인에 필요한 구성 요소(email 혹은 id, password)의 데이터 타입을 interface 형태로 지정해준다.

interface FormValues {
  email: string;
  password: string;
}

2. LoginPage안에 useForm 선언하기

아래와 같이 LoginPage 안에 useForm을 선언하고 타입은 1에서 지정한 FormValues를 활용한다.

export const LoginPage = () => {
    const {
      setError,
      register,
      handleSubmit,
      formState: { errors },
    } = useForm<FormValues>();
    
    return(
    <>로그인 페이지</>
    );
 }

3. LoginPage에 <Form 태그> 추가하기

2에서 했던 LoginPage에 <form></form>의 형식으로 로그인 Form을 구성한다. form 태그의 속성중 onSubmit를 사용하여 버튼 onClick 이벤트 시 onSubmit에 위치한 로그인 API를 호출하게 한다. 

export const LoginPage = () => {
    const {
      setError,
      register,
      handleSubmit,
      formState: { errors },
    } = useForm<FormValues>();
    
    ........ 생략 ........
    
    return (
    	<form
          className="p-4 flex flex-col space-y-4"
          onSubmit={handleSubmit((data) => {
            api
              .post('/users/valid-email-login', { email: data.email })
              .then(() =>
                login(data)
                  .then(() => push('/'))
                  .catch(
                    (e) =>
                      e.response.status === 401 &&
                      setError('password', {
                        message: '*비밀번호가 잘못되었습니다.',
                      })
                  )
              )
              .catch(
                (e) =>
                  e.response.status === 409 &&
                  setError('email', { message: '*가입된 이메일이 아닙니다.' })
              );
          })}
        >
          <TextField
            label="이메일"
            type="email"
            placeholder="이메일을 입력해주세요."
            helper={errors.email?.message}
            {...register('email', { required: '이메일을 입력해주세요' })}
          />

          <TextField
            label="비밀번호"
            type="password"
            placeholder="비밀번호를 입력해주세요."
            helper={errors.password?.message}
            {...register('password', { required: '비밀번호를 입력해주세요' })}
          />
          	<Button text="로그인" className="filled-brand-2" />
        	<Button to="/signup" text="회원가입" className="bg-gray-100" />
        </form>
    )
}