많은 input들과 각각의 유효성 검사를 위한 state 관리를 좀 더 효율적으로 하고자 react-hook-form
이라는 라이브러리를 사용하였다.
setState()
에 의해 업데이트 되고 값이 변화할 때마다 해당 Component는 re-rendering된다.setState
함수이다.우리는 React state를 “신뢰 가능한 단일 출처 (single source of truth)“로 만들어 두 요소를 결합할 수 있습니다. 그러면 폼을 렌더링하는 React 컴포넌트는 폼에 발생하는 사용자 입력값을 제어합니다. 이러한 방식으로 React에 의해 값이 제어되는 입력 폼 엘리먼트를 “제어 컴포넌트 (controlled component)“라고 합니다.
-출처: 리액트 공식문서
setState()
가 아닌 useRef
에 의해 업데이트되는데, 값을 참조할 때마다 같은 메모리 주소를 갖고 있기 때문에 제어 컴포넌트처럼 실시간으로 값을 동기화 시키지 않고, 불필요한 re-rendering 또한 없어진다.useRef
는 heap 영역에 저장되는 일반적인 자바스크립트 객체이다. setState
는 매번 다른 메모리 주소를 사용하기 때문에 값이 바뀔 때마다 re-rendering이 발생한다.기본적인 방법은 공식문서에 굉장히 잘 나와있지만 여기서는 input custom hook
을 사용한 실제 로그인 예시를 보여주고자 한다.
input custom hook에 다음과 같이 register로 한번더 감싸줘서 값을 보내주고,
<Input type="email" placeholder="example@google.com" register={{ ...register("email") }} />
register 값을 다음과 같이 받아줬다.
// InputForm.tsx
const Input = ({ ...otherProps }) => {
const registerInput = otherProps?.register
return (
<input autoComplete='off' className="w-full h-12 px-4 py-2 my-1 mb-1 transition ease-in-out bg-white border-gray-400 rounded" {...otherProps} {...registerInput} />
)
}
export default Input
react-hook-form을 사용함으로써 코드 양이 눈에 띄게 줄어들었다.
import { AiFillEye, AiFillEyeInvisible } from 'react-icons/ai'
import { Link, useNavigate } from 'react-router-dom'
import { SubmitHandler, useForm } from 'react-hook-form'
import Input from '../../InputForm'
import SignInGoogle from '../GoogleAuth'
import { accessToken } from '../../../store/initialState'
import { apiInstance } from '../../../apis/setting'
import { toast } from 'react-toastify'
import { useSetAtom } from 'jotai'
import { useState } from "react"
// input value type 설정
type FormData = {
email: string,
password: string
}
const SignInForm = () => {
const navigate = useNavigate()
const setToken = useSetAtom(accessToken)
const [showPassword, setShowPassword] = useState(false)
const { register, handleSubmit } = useForm<FormData>()
// FieldValues로 입력한 email과 password 값이 담긴다.
const clickSubmit: SubmitHandler<FormData> = async (FieldValues) => {
const { email, password } = FieldValues
try {
const submitResponse = await apiInstance.post(`/user/signin`, { email, password })
toast.success("로그인 성공!")
navigate('/money-book/dashboard')
setToken(submitResponse.data.accessToken)
} catch (e: any) {
toast.error(e.response.data.message)
}
}
return (
<section className='w-full h-full pt-[5rem]'>
<div className="flex flex-wrap items-center justify-center w-full h-full max-w-6xl px-5 mx-auto">
<div className="flex w-full flex-col md:w-[55%] lg:w-[40%]">
<h1 className="mb-8 text-4xl font-bold text-center lg:text-4xl">Sign-in</h1>
<form onSubmit={handleSubmit(clickSubmit)}>
<div className='font-semibold'>Email</div>
<Input type="email" placeholder="example@google.com" register={{ ...register("email") }} />
<div className='font-semibold'>Password</div>
<div className="relative">
<Input type={showPassword ? 'text' : "password"} placeholder="password" register={{ ...register("password") }} />
{showPassword ? <AiFillEyeInvisible onClick={() => setShowPassword(!showPassword)} className='absolute text-xl cursor-pointer right-3 top-5' /> : <AiFillEye onClick={() => setShowPassword(!showPassword)} className='absolute text-xl cursor-pointer right-3 top-5' />}
</div>
<div className='flex justify-between text-sm whitespace-nowrap'>
<p className='mb-6'>
Don't have a account?
<Link className='ml-1 text-red-600 transition ease-in-out hover:text-red-700 duration 150' to="/sign-up">
Register
</Link>
</p>
<p className='text-blue-600 transition ease-in-out hover:text-blue-700 duration 150'>
<Link to="/forgot">Forgot password?</Link>
</p>
</div>
<button className='w-full py-3 font-semibold text-white uppercase transition bg-blue-600 rounded shadow-md px-7 hover:bg-blue-700 active:bg-blue-800 hover:shadow-lg duration 150' type='submit'>Sign in</button>
<div className='flex items-center my-3 before:border-t before:border-gray-300 before:flex-1 after:border-t after:border-gray-300 after:flex-1'>
<p className='mx-3 font-semibold text-center'>OR</p>
</div>
<SignInGoogle />
</form>
</div>
</div>
</section>
)
}
export default SignInForm
공식문서
React-hook-form 왜 쓸까? 세개의 프로젝트 적용기
React: 제어 컴포넌트와 비제어 컴포넌트의 차이점