지난 포스팅에서 custom hook으로 구현한 React-query의 useMutation에 대한 제네릭 타입에 대해서 살짝 알아보았지만, useMutation에 대해 자세하게 알아보지는 않았기 때문에, 이번 포스팅에서는 낙관적 업데이트와 함께 useMutation의 옵션들과 쓰임에 대해서 조금 더 자세하게 알아보도록 하자.
낙관적 업데이트란?
일반적으로는 useMutation을 사용하여 서버 측에 API 요청을하고, API 요청이 정상적으로 진행되었다면, 응답으로 받은 서버 데이터를 통해 UI를 업데이트 한다. 이 때 낙관적 업데이트는 API 요청이 성공적이라는 것을 전제로 두고 UI를 업데이트 하게되고 사용자는 빠르게 업데이트 된 UI를 볼 수 있게 된다. API 요청이 정상적으로 이루어지지 않았을 경우에 대한 롤백 기능 또한 같이 정리할 것이다. 프로젝트의 여러 기능 중 문의 내역 삭제에 대한 낙관적 업데이트를 다뤄 보도록 하겠다.
낙관적 업데이트를 활용하지 않았을 때
import profileApi from '@apis/profile/profileApi';
import { queryClient } from 'pages/_app';
import { useMutation } from 'react-query';
const useDeleteInquiry = (inquiryId: number) => {
return useMutation<any, Error>(
'useDeleteInquiry',
() => profileApi.deleteInquiry(inquiryId),
{
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['useGetInquiry'],
});
},
},
);
};
export default useDeleteInquiry;
위의 코드는 문의 내역을 삭제하는 useMutation custom hook 예제이다. 데이터 조회에는 useQuery를 사용하지만,POST, PUT, DELETE 와 같은 데이터 변경에는 useMutation을 사용한다. 옵션으로 넣어준 onSuccess는 요청이 성공적으로 이루어진 후에 실행되는 부분으로, 해당 함수에서 queryClient의 invalidateQueries 메서드를 통해 문의 내역 조회를 담당하는 쿼리인 useGetInquiry 쿼리 키를 넘겨, 쿼리를 무효화 시킨다. 결과적으로 useMutation을 통해 API 요청 -> API 요청이 성공적 -> onSuccess 함수 실행 -> 문의 내역 조회 쿼리 무효화 -> 문의 내역 조회 쿼리 refetch -> UI 업데이트의 과정을 가진다.
위처럼 낙관적 업데이트를 사용하지 않았을 경우에는 삭제 버튼을 누르고 나서 UI가 업데이트 되기까지 조금의 딜레이가 걸린다.
낙관적 업데이트를 적용 했을 때
import profileApi from '@apis/profile/profileApi';
import { queryClient } from 'pages/_app';
import { useMutation } from 'react-query';
const useDeleteInquiry = (inquiryId: number) => {
return useMutation<any, Error>(
() => profileApi.deleteInquiry(inquiryId),
{
onMutate: async () => {
await queryClient.cancelQueries({ queryKey: ['useGetInquiry'] });
const previousValue = queryClient.getQueryData(['useGetInquiry']);
queryClient.setQueryData(['useGetInquiry'], (old: any) =>
old.filter((t) => t.id !== inquiryId),
);
return { previousValue };
},
onError: (context: any) => {
queryClient.setQueryData(['useGetInquiry'], context.previousValue);
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ['useGetInquiry'],
});
},
},
);
};
export default useDeleteInquiry;
기존에는 onSuccess 옵션만을 사용해 API 요청이 정상적으로 이루어졌을 때 쿼리를 무효화하여 refetch를 해주었지만, 낙관적 업데이트에는 onMutate, onError, onSettled 옵션을 사용했다.
onMutate은 뮤테이션 함수가 실행되기 전에 선행되는 것으로 queryClient의 cancelQueries 메서드를 통해 문의 내역 조회를 담당하는 쿼리인 useGetInquiry 쿼리 키를 넘겨 refetch가 발생하지 않도록 하여 낙관적 업데이트 시 데이터 꼬임을 방지한다. 추가적으로 뮤테이션 함수가 실행되기 전에 getQueryData 메서드로 이전 데이터의 스냅샷을 찍고, setQueryData 메서드를 통해 해당 쿼리 키를 가지는 쿼리를 수동으로 업데이트한다. setQueryDate의 업데이트 함수의 파라미터는 이전 데이터이기 때문에, 이전 문의 내역 데이터의 아이디와 넘겨준 문의 내역 아이디가 일치하지 않는 새로운 데이터를 리턴한다.
onError는 뮤테이션 함수가 실패했을 때 수행되는 것으로, onMutate에서 뮤테이션 함수가 실행되기 전 찍어놓았던 스냅샷은 context에 담겨있다. 따라서 setQueryData를 통해 문의 내역을 스냅샷 데이터로 복원한다.
onSettled는 요청이 성공 또는 실패했을 때, 두 경우 모두 수행되는 것으로, queryClient의 invalidateQueries 메서드를 통해 문의 내역 조회를 담당하는 쿼리를 무효화 시켜 refetch를 수행한다.
위처럼 낙관적 업데이트를 적용한 경우에는 빠르게 UI가 업데이트 되는 것을 확인할 수 있다.
'개발' 카테고리의 다른 글
[개발] 카카오 로그인 구현하기 (0) | 2023.03.27 |
---|---|
Yarn-berry + NextJS(typescript) 보일러플레이트 만들기 (0) | 2023.03.05 |
[개발] React-query를 활용한 데이터 필터링 (0) | 2023.02.25 |
[개발] React-query를 활용한 custom hook(ft. typescript) (0) | 2023.02.22 |
[개발] Axios 모듈화 (instance 생성 및 interceptor의 사용) (0) | 2023.02.20 |