위 슬라이드는 사용자가 알바 공고를 작성하기 위한 페이지이다.
1. 모집 내용 2.모집 조건 3. 근무 조건 총 세가지 탭을 작성해서 전체 폼 데이터를 제출해야 한다.
🤔
간단한 게시글이나 댓글 정도는 구현해본 적이 있는데 사용자 입력을 받아서 폼을 제출하는 로직을 처음 구현해보았다.
게다가 캡쳐한 이미지를 보면 각각의 필드에서 받는 데이터의 유형도 매우 다양하다는걸 알수있다^^
리액트훅폼을 제대로 안써봐서 해당 페이지를 맡으면서 제대로 써볼 기회인것 같아 잘됐다고 생각했다
🙄 페이지 구조
우선 페이지 구조는 아래와 같이 구성했다.
- 좌측에 보이는 TabMenu + 임시저장/제출 버튼
- 우측에 보이는 Form section
TabMenu 에서 선택한 값에 따라 router.push(/add?tab=query) 로 url 경로를 이동시키고
query값에 따라 매칭되는 Form section 이 렌더되는 구조이다.
이제보니 query랑 param을 헷갈렸네,,
//리액트 훅폼 관련 코드
const methods = useForm<SubmitFormDataType>({
mode: "onChange",
defaultValues: {초기값}
const {
setValue,
handleSubmit,
watch,
formState: { isDirty, isValid },
} = methods;
// 훅폼에서 관리하는 전체 데이터를 가져오는 함수
const currentValues: SubmitFormDataType = watch();
// 탭 클릭 시 option을 받아 url로 보내주는 함수
const handleOptionChange = (option: string) => {
if (option !== currentParam && isDirty) {
onTempSave();
}
const params = {
"모집 내용": "recruit-content",
"모집 조건": "recruit-condition",
"근무 조건": "work-condition",
}[option];
router.replace(`/addform?tab=${params}`);
};
// query에 따라 매칭되는 JSX 반환하는 함수
const renderChildren = () => {
switch (currentParam) {
case "recruit-content":
return <RecruitContentSection key="recruitContent" />;
case "recruit-condition":
return <RecruitConditionSection key="recruitCondition" />;
case "work-condition":
return <WorkConditionSection key="workCondition" />;
default:
return <RecruitContentSection key="recruitContent" />;
}
};
return (
<FormProvider {...methods}>
<TabMenuDropdown
options={[
{
label: "모집 내용",
isEditing: isEditingRecruitContent || currentParam === "recruit-content",
},
{ label: "모집 조건", isEditing: isEditingRecruitCondition || currentParam === "recruit-condition" },
{ label: "근무 조건", isEditing: isEditingWorkCondition || currentParam === "work-condition" },
]}
onChange={handleOptionChange}
currentParam={currentParam || ""}
/>
// ... buttons
{renderChildren()} // query에 따라 매칭되는 JSX 반환하는 함수
</FormProvider>
)
1. 리액트훅폼에서 제공하는 hook인 useForm 을 호출
리액트 훅폼에서 관리할 데이터 타입 지정 및 메서드 호출
=> 유효성 검사에 대한 mode, 초기값을 설정한다. useform 공식문서를 보면 이외에도 다른 많은 prop이 있다.
useForm()으로 호출한 methods 객체에서 필요한 메서드를 구조분해로 가져온다.
2. 리액트 훅폼에서 제공하는 FormProvider 로 전체 JSX를 감싸주었다.
FormProvider는 리액트 context API 기반으로 만들어져 컴포넌트 트리에서 prop drilling 없이 데이터를 전달해줄 수 있다.
FormProvider로 감싼 하위 컴포넌트 안에서는 useFormContext를 사용해 method를 호출할 수 있다.
React Hook Form은 비제어 컴포넌트를 기반으로 동작하여 상태 업데이트 시 불필요한 리렌더링을 최소화하도록 설계되었지만, useFormContext를 남용하거나 상태를 과도하게 업데이트하면 성능 이슈가 발생할 수 있다. 성능 최적화를 위해 필요한 부분에만 데이터를 전달하고, context 사용을 최소화하는 것이 중요하다. 고 한다 ㅎㅎ
3. 작성이 완료된 데이터는 watch() 함수로 가져와서 그대로 제출해주었다.
이것도 다사다난.. 했지만 결론은 아주 간단한 코드가 되었다. 대략 이런 식으로,,
4. 임시저장 데이터 가져오기
임시저장은 로컬 스토리지를 활용했다.
저장된 데이터는 문자열(String) 형식으로 저장되므로, JSON.parse()를 사용해 객체로 변환한다.
저장 시: 객체를 문자열로 변환 → JSON.stringify(data)
읽을 시: 문자열을 다시 객체로 변환 → JSON.parse(data)
* 임시저장
🧩 하위 컴포넌트
하위 컴포넌트인 각 section에서는 useFormContext() 로부터 method를 가져왔다.
처음에는 각 section마다 useForm을 사용해서 개별 폼으로 관리되는 문제가 있었는데
상위 컴포넌트는 `<FormProvider>`로 감싸주고 하위 컴포넌트에서 useFormContext 사용함으로써
하나의 폼데이터로 관리할 수 있었다.
이 컴포넌트들은 공고 수정 페이지에서도 같이 사용하기 때문에
공고 조회 -> 수정 시 조회한 데이터를 watch 로 값을 가져와 필드에 지정해주었다.
전체 데이터를 조회할때는 watch() 를 사용했고, 특정 필드의 값을 가져올때는 ("저장한 이름")을 넣어 불러왔다.
getValues() 와 watch() 는 모두 현재 입력된 폼 값을 즉시 가져오는 메서드이다.
watch만의 특징은
1. input 입력값을 구독하는가 (값 변경 시 실시간 업데이트)
2. 리렌더를 유발하는가 (watch는 값의 변경에 따라 리렌더를 유발한다.)
나는 변경되는 입력값을 실시간으로 가져오기 위해 watch를 사용했다.
(포스팅 하면서 배우는 리액트훅폼 ^^^ ;.. )
getValues는 호출 시점의 값을 즉시 가져오는건 동일하지만, 입력값이 변경될때 자동으로 업데이트된 값을 가져오지 않는다.
따라서 리렌더도 유발하지 않는다
=> 폼 값의 일괄적인 유효성 검사를 할때 사용
내용이 길어져
유형별 input 값 리액트 훅폼에 등록하기 포스팅은 ▼
리액트 훅 폼 (React Hook Form) 트러블 슈팅 2 - 여러 유형의 폼 데이터 제출하기
리액트 훅 폼 (React Hook Form) 트러블 슈팅 1 - 여러 탭에 걸친 폼 데이터 제출하기(watch, setValue) 리액트 훅 폼 (React Hook Form) 트러블 슈팅 1 - 여러 탭에 걸친 폼 데이터 제출하기(watch, setValue)위 슬라
memoryelim.tistory.com
'개발개발 > WorkRoot_워크루트' 카테고리의 다른 글
Ensure that calls to `useSearchParams()` are wrapped in a Suspense boundary. (0) | 2025.01.09 |
---|---|
리액트 훅 폼 (React Hook Form) 트러블 슈팅 2 - 여러 유형의 폼 데이터 제출하기 (0) | 2025.01.09 |
데이터 피커 (react-datepicker) 달력 UI 커스텀하기 (0) | 2025.01.03 |
스토리북 (storybook) 트러블 슈팅 - 2 (React-hook-form) (1) | 2025.01.03 |
스토리북 (storybook) 트러블 슈팅 - 1 (Tailwind, addon-styling, 컴포넌트 고민 ) (3) | 2025.01.03 |