- 이전부터 프로젝트를 진행하면서 에러 처리를 일관적이고 효율적으로 처리하는 방법에 대해서 고민을 많이 했고 이번 프로젝트에서는 개선된 내용을 적용해보고자 했다.
- 특히 이번 프로젝트에서는 모든 API를 호출하는 커스텀 훅을 react-query를 사용하여 만들고, 각 훅에서 API 호출 시마다 개별적으로 에러를 처리하는 것이 비효율적이라고 생각했다.
- 또한, react-query의 최신 버전인 5부터는 useQuery의 onError 콜백이 제거되었기 때문에, useQuery를 사용하는 컴포넌트 내에서 에러 처리를 해야 했기 때문에 에러 처리를 중앙화하는 방법이 훨씬 효율적이라고 판단했다.
문제정의
- 먼저 중앙화 처리 이전에 각 훅에서 API를 호출할 때마다 개별적으로 error를 처리하는 로직을 살펴보자.(에러 처리는 toast를 사용, useQuery의 경우에는 onError 콜백이 사라졌기 때문에 useMutation을 사용하는 훅을 예시로 사용)
// useChangeNickname 커스텀 훅
import { changeNickname } from '@/api/change-nickname'; // api 호출 함수
import { TNicknameChangePayload } from '@/schemas/change-nickname';
import { useMutation } from '@tanstack/react-query';
import { toast } from 'react-toastify';
const useChangeNickname = () => {
return useMutation({
mutationFn: ({ newNickname }: TNicknameChangePayload) =>
changeNickname({ newNickname }),
onError: (error) => {
if (error instanceof Error) {
toast.error(error.message || '알 수 없는 오류가 발생했습니다.', {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
},
});
};
export default useChangeNickname;
// useDeletePlaylist 커스텀 훅
import { useMutation } from '@tanstack/react-query';
import { deletePlaylist } from '@/api/delete-playlist'; // api 호출 함수
import { toast } from 'react-toastify';
const useDeletePlaylist = () => {
return useMutation({
mutationFn: ({ videoNumber }: { videoNumber: number }) =>
deletePlaylist({ videoNumber }),
onError: (error) => {
if (error instanceof Error) {
toast.error(error.message || '알 수 없는 오류가 발생했습니다.', {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
},
});
};
export default useDeletePlaylist;
- 위 두 가지의 커스텀 훅은 사용자의 닉네임을 변경할 때, 플레이리스트 항목을 삭제할 때 사용하는 훅이다.
- 한 눈에 보아도 onError 콜백 코드가 중복되고 있다는 것을 알 수 있다. 또한 만약 toast가 아닌 다른 방식으로 수정하는 등 유지보수를 위해서는, 모든 훅을 수정해야하는 번거로움도 존재하게 된다.
해결방안
- 그렇다면, 에러 중앙화 처리라는 것은 무엇이며 react-query를 사용할 때는 어떻게 구현할 수 있을까?
- 내가 적용한 방법은 react-query의 QueryClient를 사용하는 방법이었다.
- QueryClient는 react-query에서 사용되는 주요 객체로, 쿼리 상태를 관리하고 쿼리 및 변이(mutation)를 위한 설정을 제공하며 쿼리를 관리하고 캐싱, 동기화, 무효화 등을 처리하는 중심 역할을 한다.
- 따라서, 애플리케이션 내에서 하나의 QueryClient 인스턴스를 생성하여 모든 쿼리를 관리할 수 있기 때문에 이를 통해 에러 중앙화 처리를 적용했다.
// queryClient.ts
import { QueryClient, QueryCache, MutationCache } from '@tanstack/react-query';
import { toast } from 'react-toastify';
export const errorHandler = (message: string) => {
const id = 'react-query-toast';
if (!toast.isActive(id)) {
toast.error(message, {
toastId: id,
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
};
const createQueryClient = () => {
return new QueryClient({
queryCache: new QueryCache({
onError: (error) => {
if (error instanceof Error) {
errorHandler(error.message || '알 수 없는 오류가 발생했습니다.');
}
},
}),
mutationCache: new MutationCache({
onError: (error) => {
if (error instanceof Error) {
errorHandler(error.message || '알 수 없는 오류가 발생했습니다.');
}
},
}),
});
};
export default createQueryClient;
- 먼저 QueryClient 인스턴스를 생성하고 쿼리 데이터를 저장하고 관리하는 QueryCache, 변이(mutation) 작업의 상태를 관리하는 MutationCache의 onError 콜백을 사용하여 에러를 일관되게 처리하고 있다.
// provider.tsx
'use client';
import { NextUIProvider } from '@nextui-org/react';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental';
import createQueryClient from '@/lib/query-client';
import { useState } from 'react';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
export function NextUIProviders({ children }: { children: React.ReactNode }) {
return <NextUIProvider>{children}</NextUIProvider>;
}
export function ReactQueryProviders({ children }: React.PropsWithChildren) {
const [client] = useState(createQueryClient);
return (
<QueryClientProvider client={client}>
<ReactQueryStreamedHydration>{children}</ReactQueryStreamedHydration>
<ReactQueryDevtools />
<ToastContainer />
</QueryClientProvider>
);
}
- provider.tsx 파일에서 QueryClientProvider를 사용하여 생성한 QueryClient를 주입하였다.
결과
- 사용자의 닉네임 변경에 실패하였을 때, toast와 에러 메시지가 정상적으로 출력되는 것을 확인할 수 있다.
- 에러 중앙화 처리를 통해 애플리케이션의 모든 에러를 한 곳에서 관리하고 처리함으로써, 개발 과정에서 일관성을 유지하고, 코드의 가독성을 높이며, 유지보수를 훨씬 쉽게 만들 수 있었다.
배운 점
- 항상 에러를 효율적으로 처리하는 방법에 대해 고민이 많았었는데, 이번에 적용한 에러 중앙화를 통해 에러 처리를 일관되게 하고, 코드 중복을 줄이며, 유지보수를 용이하게 할 수 있어서 스스로 한 단계 더 나아갔다는 생각이 들어 만족스럽다.
참고 : https://velog.io/@zooyaho/React-Query-%EC%A4%91%EC%95%99-%EC%A7%91%EC%A4%91%ED%99%94
'프로젝트 > You-Together' 카테고리의 다른 글
next.js에서 Open Graph 적용하기 (1) | 2024.09.23 |
---|---|
Hydration API를 사용한 데이터 프리패칭을 통한 초기 로딩 속도 개선 (0) | 2024.08.29 |
You-Together 회고 (0) | 2024.07.26 |
실시간 동기화를 위한 웹 소켓 사용: 영상 싱크 및 채팅 구현 (1) | 2024.07.26 |
SEO 처리하기 (0) | 2024.07.25 |