이번에는 사용자 경험을 더 좋게 하기 위해 낙관적 업데이트를 적용해보았다.
낙관적 업데이트 (Optimistic Update)
낙관적 업데이트란 클라이언트에서 먼저 UI를 업데이트 시켜주고 서버에 요청을 하는 것이다.
기존 방식은 서버 요청 → 응답 → UI 업데이트
였다면 UI 업데이트 → 서버 요청 → 응답
으로 사용자 입장에서 빠르게 변경사항을 확인할 수 있어 사용자 경험을 보다 좋게 할 수 있는 전략이다.
react-query로 낙관적 업데이트 구현하기
const UpdateDiary = (studyId: string) => {
const queryClient = useQueryClient();
return useMutation(
async (newDiary: IDiary) => {
const response = await instance.put(
`/study/${studyId}/diary/${newDiary.id}`,
newDiary
);
return response;
},
{
onMutate: async (newDiary) => {
// 1. 쿼리 get 요청 취소 (이전 데이터가 업데이트 데이터 덮어쓰기를 방지)
await queryClient.cancelQueries(["diaryList", studyId]);
// 2. 이전 값 저장
const prevDiaryList: IDiary[] =
queryClient.getQueryData(["diaryList", studyId]) || [];
// 3. 낙관적 업데이트
queryClient.setQueryData(["diaryList", studyId], () =>
prevDiaryList?.map((diary) =>
diary.id === newDiary.id ? { ...diary, ...newDiary } : diary
)
);
return { prevDiaryList };
},
// 에러 발생 시 롤백
onError: (error, newDiary, context) => {
if (context?.prevDiaryList) {
queryClient.setQueryData(
["diaryList", studyId],
context.prevDiaryList
);
}
},
onSettled: () => {
// 요청 성공 여부와 상관없이 쿼리를 무효화해 최신 데이터를 받아오도록 한다.
queryClient.invalidateQueries(["diaryList", studyId]);
},
}
);
};
onMutate
는 mutate 함수가 실행되기 전에 호출된다. queryClient.cancelQueries(key)
로 낙관적 업데이트로 변경된 데이터를 refetch에 의해 이전 데이터로 덮어쓰지 않도록 방지한다.queryClient.getQueryData(key)
로 이전 데이터를 저장한다. queryClient.setQueryData(key,newData)
로 데이터를 변경시킨다. return { 이전 데이터 }
로 이전 데이터를 반환한다. 이는 서버 요청에서 에러가 발생했을 때 롤백 시키기 위함이다. 이전 데이터는 onError
에 context
안에 들어간다.onError
는 요청이 실패하였을 때 호출되는 함수이다. ( 세 번째 인자에 context가 위치한다. )queryClient.setQueryData(key,이전 데이터)
로 요청이 실패하였을 때 이전 값으로 되돌린다. onSettled
는 요청의 성공 유무와 상관없이 호출되는 함수이다.queryClient.invalidateQueries(key)
를 통해 쿼리를 무효화 시켜 새로운 값을 불러오게 한다.