-
[React] react-hook-form (Basic)프로그래밍/React 2023. 12. 8. 11:17
개요
- react-hook-form을 도입하계 된 계기
- react-hook-form
- 마무리
제어 컴포넌트에서 벗어나 비제어 컴포넌트 방식으로 form을 관리한다?
react-hook-form 도입
라이브러리 없이 제어 컴포넌트로 form을 다루자)
React에서 제어컴포넌트란 React를 통해서 제어하게 되는 컴포넌트이다.
간단하게 이름과 아이디, 비밀번호, 전화번호, 이메일을 받는 form을 만들 때 state를 통해서 값을 관리할 것이다.
제어컴포넌트로 form을 다룰 시에 나올 수 있는 모습이다
더보기import React, { useState } from "react"; import InputText from "../component/InputText"; import { Label, Row } from "../style"; enum Occupation { Student = "student", Professor = "professor", } function NoHookForm() { const [occupation, setOccupation] = useState(Occupation.Professor); const [name, setName] = useState(""); const [id, setId] = useState(""); const [pwd, setPwd] = useState(""); const [phone, setPhone] = useState(""); const [email, setEmail] = useState(""); const [errors, setErrors] = useState({ name: { invalid: false, message: "이름이 너무 깁니다.", }, id: { invalid: false, message: "id는 3글자 이상, 20글자 이하여야 합니다.", }, pwd: { invalid: false, message: "비밀번호는 10자 이하여야 합니다.", }, phone: { invalid: false, message: "전화번호 형식에 맞지 않습니다.", }, email: { invalid: false, message: "이메일 형식에 맞지 않습니다.", }, }); const handleName = (event: React.ChangeEvent<HTMLInputElement>) => { setName(event.currentTarget.value); }; const handleId = (event: React.ChangeEvent<HTMLInputElement>) => { setId(event.currentTarget.value); }; const handlePwd = (event: React.ChangeEvent<HTMLInputElement>) => { setPwd(event.currentTarget.value); }; const handlePhone = (event: React.ChangeEvent<HTMLInputElement>) => { setPhone(event.currentTarget.value); }; const handleEmail = (event: React.ChangeEvent<HTMLInputElement>) => { setEmail(event.currentTarget.value); }; const handleOccupation = (event: React.ChangeEvent<HTMLInputElement>) => { setOccupation(event.currentTarget.value as Occupation); }; const handleSubmit = (event: React.SyntheticEvent) => { event.preventDefault(); if (name.length > 10) { setErrors((prev) => ({ ...prev, name: { ...prev.name, invalid: true, }, })); } if (id.length < 3 || id.length > 20) { setErrors((prev) => ({ ...prev, id: { ...prev.id, invalid: true, }, })); } // ...등.. 에러 처리 이후 로직 필요. }; return ( <form> <Row> <Label>Occupation: </Label> <input type="radio" name="occupation" value="professor" defaultChecked onChange={handleOccupation} /> <input type="radio" name="occupation" value="student" onChange={handleOccupation} /> </Row> <Row> <Label>name: </Label> <InputText value={name} onChange={handleName} /> {errors.name.invalid ? ( <p className="error">{errors.name.message}</p> ) : null} </Row> <Row> <Label>id: </Label> <input type="text" value={id} onChange={handleId} /> {errors.id.invalid ? ( <p className="error">{errors.id.message}</p> ) : null} </Row> <Row> <Label>pwd: </Label> <input type="text" value={pwd} onChange={handlePwd} /> </Row> <Row> <Label>phone: </Label> <input type="text" value={phone} onChange={handlePhone} /> </Row> {occupation === Occupation.Professor ? ( <Row> <Label>e-mail: </Label> <input type="text" value={email} onChange={handleEmail} /> </Row> ) : null} <button onClick={handleSubmit}>Submit</button> </form> ); } export default NoHookForm;
더 짧게 만들 수도 있겠지만 기본적인 구성은 이렇게 될 것이다.
validation을 비롯해 state관리 및 DX에도 상당히 불편한 점이 많고 컴포넌트 분리도 해주어야 랜더링 성능이 개선되기에 여러가지 고려사항이 더 생기는 문제가 있다.
react-hook-form을 활용해보자
install)
pnpm add react-hook-form
useForm)
가장 많이 활용되는 hook으로 option에 어떠한 것이 들어가는 지 알고 있는 것이 중요하다 할 수 있다 (Docs 참고하자)
- mode : validation 전략을 설정하는데 활용되며, 랜더링 성능과도 연관있음 (onSubmit, onChagne ...)
- defalutValue : 초기값을 설정 (제공하지 않는다면 초기값은 undefined)
더보기export type UseFormReturn<TFieldValues extends FieldValues = FieldValues, TContext = any> = { watch: UseFormWatch<TFieldValues>; getValues: UseFormGetValues<TFieldValues>; getFieldState: UseFormGetFieldState<TFieldValues>; setError: UseFormSetError<TFieldValues>; clearErrors: UseFormClearErrors<TFieldValues>; setValue: UseFormSetValue<TFieldValues>; trigger: UseFormTrigger<TFieldValues>; formState: FormState<TFieldValues>; resetField: UseFormResetField<TFieldValues>; reset: UseFormReset<TFieldValues>; handleSubmit: UseFormHandleSubmit<TFieldValues>; unregister: UseFormUnregister<TFieldValues>; control: Control<TFieldValues, TContext>; register: UseFormRegister<TFieldValues>; setFocus: UseFormSetFocus<TFieldValues>; };
useForm을 통해서 return받는 값의 타입
useForm return value)
- register : 해당 함수를 통해서 input을 다룰 수 있다 form의 객체를 리턴 받아서 destructuring한다.
- option이 다양하니깐 Docs를 보면서 도움이 될만한 것을 찾아보자!
<input type="text" {...register("email", { pattern: { value: /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i, message: "이메일 형식에 맞지 않습니다.", }, })} />;
- formState : 에러를 비롯한 유용한 정보를 가지고 있다. 에러가 존재하지 않다면 해당 객체는 빈 객체다 된다.
- option이 다양하니깐 Docs를 보면서 도움이 될만한 것을 찾아보자!
- watch : 비제어 컴포넌트로 form을 구현할 때 조건에 따라서 필드에 노출되어야 할 때가 있다 이럴 때 사용하며 실시간으로 값을 구독하여 체크해 준다. 해당 값에 따라서 리렌더링을 일으킬 수 있다
- 초기값을 주지 않는다면?? undefined...
const {id, name, pwd, ...watch} = watch(); //전체 필드를 리턴 const id = watch("id");
- getValues : 값을 추적할 수 있는 방법 중 리렌더링을 일으키지 않는 방법으로 watch와 비교해서 선택적으로 사용하면 됨!
const handleEvent = (event) => { const value = getValues("name"); setState(value); } // 하지만 이 코드는 state로 관리를 하니 리렌더링이 일어난다
- reset : success과정에서 reset처리를 해준다면 올바른 타이밍에 초기화를 할 수 있다 reset은 이런 것을 도와준다
useProject(id, { enabled: !!id, refetchOnMount: "always", onSuccess: (projectData: IProject) => { reset(projectData); }, [reset], }); useProject는 custom hook이고 서버데이터를 불러오는 함수라고 생각하면 될 듯
- handleSubmit : tkdydwkr사용자가 등록버튼을 눌렀을 때 onSubmit이벤트가 발생하는데 서버에 데이터를 넘기기 전에 해당 데이터에 대한 검증을 할 수 있다고 생각하면 된다. 그렇기에 우리가 만든 onSubmit함수를 handleSubmit을 감싸게 되며 또한 event.preventDefault()도 할 필요 없다. setError와 setFocus 등 다양한 기능이 있다.
cosnt { handleSubmit } = useForm(); const onSubmit = (data: IForm) => { // data 가 최종 데이터 }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Row> ... <button>Submit</button> </form> ); }
마무리
react-hook-form이라는 라이브러리를 통해서 DX적으로 줄어들고 개선된 코드를 작성할 수 있을 것이라고 생각이 된다.
다음과 같은 선정이유를 기억하고 필요한 경우를 잘 따져보면서 사용해보자
- 비제어 컴포넌트를 활용한 짧아진 코드와 좋아진 가독성, 간단한 조작 (직관적인 코드)
- 관리해야하는 state수의 감소와 랜더링 횟수 최소화 (UI 개선 및 state로 인한 버그 최소화)
- 라이브러리 활용에 따른 차후 유지보수 시 다른 사람들도 보기 쉬울 수 있다 (Docs가 있기에)
'프로그래밍 > React' 카테고리의 다른 글
[Vite] 절대경로 설정 (0) 2024.01.03 [React] useState의 동작원리 (0) 2023.12.16 [React] 새로운 리액트 문서에서 제시하는 9가지 권장 사항 (1) 2023.12.06 [React] Suspense (2) 2023.11.22 [Hooks] useEffect (1) 2023.11.22