직원 정보 수정 모달을 만들면서 데이터 흐름이 좀 헷갈렸다.
API에서 데이터 가져오고, 폼에 채우고, 수정하고, 다시 저장하는 과정이 생각보다 복잡했다.
API 조회 → useForm에 초기값 세팅 → 사용자가 수정 → mutate 호출 → 성공 → form.reset + 캐시 업데이트
하나씩 뜯어보자.
const { data: officeData } = useGetUserOffice(userId.toString())
TanStack Query로 서버에서 데이터를 가져온다. officeData에 기존 직원 정보가 담긴다.
const form = useForm<UserOfficeDtoType>({
defaultValues: {
...defaultData,
department: mapLabelToValue(officeDepartmentNameOption, defaultData.department),
position: mapLabelToValue(officePositionOption, defaultData.position),
// ...
}
})
여기서 좀 헤멤;;
서버에서 오는 값이 "경영지원"(label)인데, 폼에서는 "MANAGEMENT_SUPPORT"(value)로 저장해야 했다.
mapLabelToValue 함수로 변환해서 넣어줬다.
<MultiInputBox name='department' form={form} labelMap={MEMBER_INPUT_INFO.office} />
MultiInputBox 내부에서 Controller가 폼 상태를 관리한다.
<Controller
name={name}
control={form.control}
render={({ field }) => (
<TextField
value={field.value}
onChange={field.onChange} // 여기서 form 상태 업데이트
select
/>
)}
/>
드롭다운에서 값을 바꾸면 field.onChange가 호출되고, form 내부 상태가 바뀐다. 이 시점에서 서버에는 아무것도 안 간다.
const { mutateAsync } = useMutateSingleMember<UserOfficeDtoType>(userId, 'office')
const save = form.handleSubmit(async data => {
const newOffice = await mutateAsync(data)
// ...
})
form.handleSubmit이 폼 데이터를 모아서 data로 넘겨준다. 이걸 mutateAsync로 서버에 보낸다.
처음에 mutate랑 mutateAsync 차이를 몰랐다. mutateAsync는 Promise를 반환해서 await로 기다릴 수 있다. 저장 성공 후에 뭔가 처리해야 하면 mutateAsync를 쓰면 된다.
const save = form.handleSubmit(async data => {
const newOffice = await mutateAsync(data)
// 폼 상태 리셋 (dirty 플래그 초기화)
form.reset({
...newOffice,
department: newOffice.department ?? '',
// ...
})
console.log('office 정보 수정 완료')
})
여기서 form.reset을 안 하면 문제가 생긴다. 저장 후에도 폼이 "수정됨" 상태로 남아있어서, 창을 닫을 때 "변경사항이 있습니다" 경고가 뜬다.
form.reset(newData)를 호출하면 폼의 defaultValues가 새 데이터로 바뀌고, isDirty가 false가 된다.
return useMutation({
mutationFn: data => putSingleMember(memberId, data),
onSuccess: data => {
queryClient.setQueryData(queryKey, (prev) => ({
...prev,
[requestInfo.dtoKey]: { ...prev[requestInfo.dtoKey], ...data }
}))
}
})
onSuccess에서 queryClient.setQueryData로 캐시를 업데이트한다. 이러면 다른 곳에서 같은 데이터를 조회할 때 서버 요청 없이 바로 새 데이터를 보여줄 수 있다.
처음에 API 응답이 오면 defaultValues가 자동으로 바뀌는 줄 알았다. 아니었다. defaultValues는 최초 1회만 적용된다. 나중에 값을 바꾸려면 form.reset(newValues)를 써야 한다.
저장 버튼을 "변경사항 있을 때만" 활성화하고 싶었다.
<Button disabled={!form.formState.isDirty}>저장</Button>
근데 탭이 여러 개라서 각 탭의 isDirty를 모아서 체크해야 했다. useImperativeHandle로 각 탭 컴포넌트의 dirty 상태를 부모에서 접근할 수 있게 했다.
대충 데이터가 서버 → 폼 → 서버 → 캐시 → 폼으로 돌아오는 흐름을 이해하니까 어디서 뭘 해야 하는지 감이 잡혔다.