티스토리 뷰
⭐ React-hook-form
React Hook Form은 React에서 간단하고 효율적으로 폼을 관리하기 위한 라이브러리입니다.
폼 관리 방식
React Hook Form은 Uncontrolled 방식과 Controlled 방식이 있다.
- Uncontrolled : register 함수를 사용하고 두 번째 파라미터를 사용하여 유효성 검증이 가능하며 DOM과 직접적으로 연결되어 성능이 우수하다.
- Controller : Controller 컴포넌트의 rules 속성을 사용하여 유효성 검증이 가능하며 주로 외부 UI 라이브러리와 통합 시 사용한다.
API : useForm Hook
- register : Uncontrolled 방식으로 사용하기 위해 사용됩니다. 요소의 속성으로 전달되어야 하는 값들을 반환하는 함수입니다.
<input {...register('username', { required: true })} />
- control : Controlled 방식으로 사용하기 위해 사용됩니다. UI 라이브러리와 함께 사용할 때 자주 사용됩니다.
<Controller
name="username"
control={control}
rules={{ required: "Username is required" }}
render={({ field }) => <TextField {...field} />}
/>
handleSubmit : 폼 제출 시 호출되는 함수로 유효성 검증을 통과한 데이터를 콜백으로 전달한다.
<form onSubmit={handleSubmit(onSubmit)}>
...
</form>
watch : 사용자 입력 값의 변화를 감시하기 위한 함수입니다.
formState : 폼의 상태를 저장하는 객체로, 대표적으로 errors 객체를 통해 유효성 에러 정보를 가져올 수 있습니다.
const { errors } = formState;
Controller 컴포넌트
Controller 컴포넌트를 사용하면 UI 라이브러리와 React Hook Form을 함께 사용할 수 있습니다.
- name : 요소를 구분하기 위한 값으로 같은 폼으로 묶여 있는 요소끼리 유니크해야 합니다.
- control : useForm 훅의 반환 값인 control을 그대로 전달하면 됩니다.
- render : 화면에 렌더링할 요소를 반환하는 함수입니다.
- defaultValue : 컴포넌트 수준에서 필드 초기값을 설정합니다.
- rules : 유효값 검증 규칙 설정합니다.
<Controller
name="email"
control={control}
defaultValue=""
rules={{ required: "Email is required" }}
render={({ field }) => <TextField {...field} />}
/>
Controller 활용하기
저는 React Hook Form의 Controller와 공통 컴포넌트를 활용하여 반복되는 필드 작성과 유효성 검증 로직 관리를 해결했습니다.
TextFieldItem 컴포넌트
- Controller를 사용하여 유효성 검증과 외부 라이브러리 MUI 통합
- props로 itemKey, title, requiredText, control, errors를 받아 동적으로 동작
- sx 스타일링을 통해 에러 메시지를 애니메이션 효과로 표시하여 사용자 경험을 향상
const TextFieldItem = memo(({ itemKey, title, requiredText, control, errors }) => {
return (
<Box>
<Typography fontWeight="bold" sx={{ mb: 1 }}>
{title}
</Typography>
<Controller
name={itemKey}
control={control}
defaultValue=""
rules={{ required: requiredText }}
render={({ field }) => (
<>
<TextField
{...field}
fullWidth
variant="outlined"
placeholder={requiredText}
error={!!errors}
/>
<FormHelperText
sx={{
transition: "all 0.4s ease-in-out",
opacity: errors ? 1 : 0,
visibility: errors ? "visible" : "hidden",
color: theme.palette.error.main,
}}
>
{errors?.message}
</FormHelperText>
</>
)}
/>
</Box>
);
});
useForm 활용
- handleSubmit, control, formState.errors를 사용하여 폼 데이터를 검증 및 관리
- onBlur 모드 설정으로 블러 이벤트에서 유효성 검증 실행
const { handleSubmit, control, formState: { errors } } = useForm({ mode: "onBlur" });
const handleLogin = useCallback((data) => {
console.log("Form Data:", data);
navigator("/");
}, [navigator]);
사용 사례
- TextFieldItem 컴포넌트를 활용하여 간결하고 직관적인 폼 필드를 구현
- 아래와 같이 다양한 필드를 공통 컴포넌트로 재사용 가능
<TextFieldItem
itemKey="id"
title="ID"
requiredText="아이디를 입력해주세요"
control={control}
errors={errors.id}
/>
<TextFieldItem
itemKey="password"
title="Password"
requiredText="비밀번호를 입력해주세요"
control={control}
errors={errors.password}
/>
⭐ 공통 컴포넌트에서 Action 재사용성 높이기
React Hook Form을 사용하면서 SplitLayout이라는 공통 컴포넌트를 설계했습니다.
이 컴포넌트는 입력 영역을 props로 받아오며, 항상 버튼을 포함하는 구조를 가지고 있습니다.
특징적인 요구사항은 버튼을 클릭했을 때, 폼을 제출(submit)하는 동작을 수행해야 한다는 점이었습니다.
초기
SplitLayout 자체를 <form onSubmit={handleSubmit(handleLogin)}>로 감싸는 구조를 사용했습니다.
이 방식은 다음과 같은 문제점을 드러냈습니다:
- 가독성 저하: <form> 내부에 너무 많은 불필요한 태그가 포함되니 유지보수와 가독성이 떨어짐
- 재사용성 감소: SplitLayout이 특정 로직에 종속되어 다른 폼에서 재사용하기 어려움
개선
- 폼 제출과 관련된 로직을 SplitLayout 내부로 이동.
- onSubmit을 props로 전달받아 폼 제출 시 동작을 유연하게 처리
const Login = () => {
const navigator = useNavigate();
const {
handleSubmit,
control,
formState: { errors },
} = useForm({ mode: "onBlur" });
const handleLogin = useCallback(
(data) => {
console.log("data", data);
navigator("/");
},
[navigator]
);
return (
<SplitLayout
title="Log in"
buttonTitle="Log in"
buttonAction={handleSubmit(handleLogin)}
>
<Box
display="flex"
flexDirection="column"
justifyContent="space-between"
flex="1"
gap={10}
>
<Stack spacing={6}>
<TextFieldItem
itemKey="id"
title="ID"
requiredText="아이디를 입력해주세요"
control={control}
errors={errors.id}
/>
<TextFieldItem
itemKey="pw"
title="PW"
requiredText="비밀번호를 입력해주세요"
control={control}
errors={errors.pw}
/>
</Stack>
</Box>
</SplitLayout>
);
};
const SplitLayout = ({ title, children, buttonTitle, buttonAction }) => {
return (
<Grid container sx={{ height: "100vh", overflow: "auto" }}>
<Grid size={6}>
// 생략
<form onSubmit={buttonAction}>
<Box padding={4}>{children}</Box>
<Box display="flex" justifyContent="center">
<Button
variant="contained"
type="submit"
sx={{
width: "304px",
height: "40px",
margin: "auto",
borderRadius: "40px",
backgroundColor: "hyundai.default",
}}
>
{buttonTitle}
</Button>
</Box>
</form>
</Grid>
</Grid>
);
};
장점
- 가독성 향상: 폼 관련 로직이 SplitLayout에 캡슐화되어 관리가 쉬워짐.
- 재사용성 증가: 버튼과 폼 레이아웃을 관리하는 SplitLayout은 다양한 폼에서 재사용 가능.
- 유연성 확보: onSubmit을 외부에서 전달받아 다양한 동작을 쉽게 구현.
결론
SplitLayout 내부에서 폼 로직을 처리하며, 자식 컴포넌트는 단순히 입력 필드와 폼 동작을 전달하는 역할만 수행합니다.
결과적으로 컴포넌트의 가독성과 재사용성을 모두 높일 수 있는 깔끔한 구조를 제공하게 됩니다.
참고
'FrontEnd > React' 카테고리의 다른 글
[react-virtuoso] 대용량 리스트 렌더링 성능 개선하기 (성능 측정) (0) | 2025.03.20 |
---|---|
[React] lazy()와 Suspense를 활용한 동적 로딩 및 로딩 페이지 구현 (0) | 2025.02.04 |
[vite-plugin-svgr] Vite와 React에서 SVG 파일을 자동으로 Import하고 사용하는 방법 (0) | 2025.01.13 |
[Zustand] Immer,Actions분리로 데이터그리드 상태 관리 리팩토링하기 (0) | 2024.12.24 |
[React] 폼의 입력 필드 리렌더링 최적화하기 : React.memo 활용 (+useMemo 비교) (1) | 2024.11.28 |
- Total
- Today
- Yesterday
- frontend
- package-lock
- Chart
- 얕은복사
- Legend
- javascript
- 객체
- chartjs
- piechart
- Location
- SCSS
- 객체복사
- 프론트엔드
- VUE
- x축스크롤
- package
- Figma Style
- echarts
- npm
- 환경설정
- vscode
- Vscode단축키
- npm install
- 깊은복사
- BarChart
- Figma 버튼
- web
- Figma 기초
- SASS
- figma
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |