-
[React-Hook-Form] FormData 관리하기구버전/React 2024. 5. 29. 23:45
개요
1. 왜 사용해야하는가?
2. 주요 함수 간단 정리
3. React-Hook-Form 사용법?
✅ Why?
📦 제어 vs 비제어 컴포넌트
→ 제어 컴포넌트
React에 의해 값이 제어되는 입력 폼 엘리먼트를 뜻하며, 일반적으로 state를 통해서 input값을 받아올 때의 상태를 말한다.
- 실시간으로 값이 동기화되므로 값의 변화에 따라서 리랜더링이발생한다.
- 사용자의 입력 폼이 늘어날수록 랜더링되는 요소가 늘어나 불필요한 연산이 발생한다.
- 유지보수에 힘들고 유효성 검사까지 진행한다면 코드가 매우 길어져 가독성이 나빠진다.
const [value, setValue] = useState(""); <input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
→ 비제어 컴포넌트
React에 의해 값이 제어되지 않는 엘리먼트를 뜻하며, 실시간으로 값을 동기화시키지 않고, submit 버튼과 같은 특정 이벤트가 발생 시 값을 받아온다.
- 실시간으로 값이 동기화되지 않아 불필요한 리랜더링이 발생하지 않아 성능 개선에 필수적인 요소이다.
- 폼이 늘어나도 관리한 state값이 늘어나지 않는다.
- 실시간으로 값이 동기화되지 않기에 실시간 유효성 검사, 버튼의 조건부 활성화 등의 기능을 사용할 수 없다.
const inputRef = useRef() const onClick = () => { console.log(inputRef.current.value); }; <div className="App"> <input ref={inputRef} /> <button type="submit" onClick={onClick}> 전송 </button> </div>
Recat-Hook-Form은 이러한 제어, 비제어 컴포넌트의 장점만을 그대로 살린 라이브러리로 비제어 컴포넌트의 불필요한 리렌더링 억제 및 제어 컴포넌트의 실시간 동기화 등을 가능하게 해주며, 훨씬 적은 코드로 더 나은 성능을 제공해준다.
📦 그 외 장점
→ 작은 사이즈, 꾸준한 업데이트
라이브러리 자체의 사이즈도 작고, 꾸준히 업데이트되고 있다.
2024.05 기준 → 친절한 공식문서, 활발한 커뮤니티
다양한 커뮤니티 및 기업에서 선정될 만큼 많이 사용되어 활발한 커뮤니티를 형성하고 있으며, 공식 문서의 설명과 예제가 잘 나와있어 쉽게 배울 수 있다.
→ 뛰어난 호환성
TS를 기본적으로 지원하며, 다양한 입력 타입(텍스트, 체크박스, 셀렉터 등등)에 대해서도 사용을 할 수 있으며, React Hook을 사용하기에 함수 함수 컴포넌트에서 간편하게 사용할 수 있다. 또한 다른 라이브러리와 통합하기 용이하여 유효성 검사 구현 시 유효성 검사 라이브러리(Yup, Joi, Zod 등등)와 통합하여 손쉽게 규칙을 정의할 수 있다.
✅ 주요 API
📦 useForm
form을 쉽게 관리할 수 있는 API로 parameter로 여러 옵션을 넣을 수 있지만 가장 주요하게 mode와 defalutValue가 있다.
- mode: 유저가 submit을 하기 전에 설정한 모드로 유효성 검사를 진행한다.
- defalutValue: form에 대한 defalut값으로 동기/비동기 모두 지원한다.
import { useState } from "react"; import { useForm, SubmitHandler } from "react-hook-form"; export default function App() { const { register, handleSubmit, formState } = useForm<{firstName: string}>({ mode: "onChange", defaultValues: { firstName: "", }, }); const { errors } = formState; const onSubmit: SubmitHandler<IFormInput> = (data) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName", { required: "필수", maxLength: { value: 10, message: "최대 10자" }, pattern: { value: /^[A-Za-z]+$/i, message: "알파벳" } })} /> <input type="submit" /> <p>errors.firstName.message: {errors.firstName?.message}</p> <p>errors.firstName.types: {JSON.stringify(errors.firstName?.types)}</p> </form> ); }
→ register
입력 필드를 react-hook-form 폼 상태와 연결하는 함수로 입력 필드의 name 속성을 등록하고, 필요한 유효성 검사 규칙을 설정할 수 있다. 반환 값은 보통 구조분해를 사용해서 전달이 된다.
- 전달되는 값: onChagne, onBlur, name, ref
→ handleSubmit
유효성 검사에 통과된 폼 데이터를 받을 수 있는 콜백 함수를 제공하는 함수로 첫번째 parameter는 resolve상태를 받을 수 있는 콜백 함수, 두번째 parameter는 reject상태에서 실행되는 콜백함수이다.
const onSubmit = (data: IFormInput) => console.log(data); const onError = (error: any) => console.log(error); <form onSubmit={handleSubmit(onSubmit, onError)}>
→ formState
form 전체 상태 정보를 담고 있는 객체로 폼의 상태를 알고 싶을 때 사용할 수 있는 값이다.
const { ... formState: { errors, isDirty, isLoading, isSubmitted, isSubmitSuccessful, isSubmitting, isValid, submitCount, defaultValues, dirtyFields, touchedFields } } = useForm<{ test: string }>({ mode: "onChange", defaultValues: () => { return new Promise((resolve) => { setTimeout(() => resolve({ test: "123" }), 1000); }); } });
→ watch
특정요소를 감시하여 그 요소의 값을 반환하게 해준다. 해당 요소의 값을 가져와서 사용해야할 때 유용하게 사용되는 함수이다.
- watch: (names: string | stsring[] | (data, options) => void, defaultValue: Object | unknown ) => unknown
import { useEffect } from "react"; import { useForm, SubmitHandler } from "react-hook-form"; export default function App() { const { register, handleSubmit, watch } = useForm<{firstName: string}>(); const watchFirstName = watch("firstName"); const onSubmit: SubmitHandler<IFormInput> = (data) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("firstName")} /> <input type="submit" /> </form> ); }
→ reset
폼의 입력 값을 초기 상태로 되돌리는 함수로 재설정 혹은 초기화에 사용된다
✅ How?
📦 Install
npm install react-hook-form
📦 Example
import { useState } from "react"; import { useForm } from "react-hook-form"; import Header from "./Header"; export function App() { const { register, handleSubmit } = useForm(); const [data, setData] = useState(""); return ( <form onSubmit={handleSubmit((data) => setData(JSON.stringify(data)))}> <Header /> <input {...register("firstName")} placeholder="First name" /> <select {...register("category", { required: true })}> <option value="">Select...</option> <option value="A">Option A</option> <option value="B">Option B</option> </select> <textarea {...register("aboutYou")} placeholder="About you" /> <p>{data}</p> <input type="submit" /> </form> ); }
📦 모듈화
- register의 option을 별도로 관리하고, 전달할 수 있는 값에 대해서 전달하여 코드를 간결하게 구성할 수 있었다.
// page > signup.tsx import { useForm } from 'react-hook-form'; import { ExtendedSignupForm } from '@/types/user'; import InputArea from '@/components' const SignupPage = () => { const { watch, register, handleSubmit, formState: { errors }, } = useForm<{email: string}>({ mode: 'onChange', defaultValues: {email: ''}, }); const onSubmit = (data: {email: string}) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> {essentiolFormData.map(({label, value, registerOptions})=> ( <InputArea key={value} label={label} register={register} errors={errors} registerOptions={registerOptions} value={value} />))} <button type='submit'>submit</button> </form> ); }; export default SignupPage; // InputArea.tsx import { FieldErrors, UseFormRegister } from 'react-hook-form'; import { ExtendedSignupForm } from '@/types/user'; import { SignupValues } from '@/consts/signup'; import * as S from './styles'; interface InputAreaProps { label: string; value: SignupValues; register: UseFormRegister<ExtendedSignupForm>; errors: FieldErrors<ExtendedSignupForm>; registerOptions?: object; } const InputArea = ({ label, value, register, errors, registerOptions, }: InputAreaProps) => { return ( <S.Container> <S.Label htmlFor={value} children={label} /> <S.Input {...register(value, registerOptions)} id={value} /> {errors[value] && ( <S.ErrorMessage color="red" typography="t6"> {errors[value]?.message} </S.ErrorMessage> )} </S.Container> ); }; export default InputArea; // registerOptions export const essentiolFormData = [ { label: '이메일', value: 'email', registerOptions: { required: '이메일을 입력해주세요', pattern: { value: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: '이메일 형식을 지켜주세요', }, }, }, ]
직접 사용해보았을 때 결국 실시간 유효성 검사를 하기 위해서는 제어 컴포넌트 형식으로 돌아갈 수 밖에 없다는 것이 아쉬웠다. 비제어 방식으로 사용할 수도 있겠지만 그렇게 하면 submit 이벤트가 발생하기 전에는 유효성 검사가 실시되지 않아 UX에 좋은지는 고려해보아야 할 요소이고 만약 react-hook-form을 사용하지 않고 구현한다면 디바운싱을 이용해서 유효성 검사 시기를 조절한다면 좀 더 성능적으로 나은 form을 구현할 수 있을 것 같다.
📌 Reference
'구버전 > React' 카테고리의 다른 글
[React] ErrorBuondary의 사용방법 (0) 2024.05.21 [React] useLayoutEffect의 활용 (0) 2024.04.02 [React] 18버전의 추가된 새로운 기능 (step- 1) (0) 2024.02.17 [React] 고차 컴포넌트 (HOC, High Order Component) (0) 2024.01.20 [Vite] 절대경로 설정 (0) 2024.01.03