React Query 좀 더 우아하게 사용해보기

React Query로 커스텀 훅, 전역 로딩, 에러 핸들링 사용 해보기


react-query를 어떻게 더 깔끔하고 실무적으로 사용할 수 있는지


1. 커스텀 훅으로 feature 별로 분리하기

  • 커스텀 훅으로 feature별로 관리하면 관리하기도 좋고 유지 보수에 좋다.
export function useStaff() {
  const [filter, setFilter] = useState("all");
  const fallback: Staff[] = [];
 
  const { data: staff = fallback } = useQuery({
    queryKey: [queryKeys.staff],
    queryFn: getStaff,
  });
 
  return { staff, filter, setFilter };
}
export function useTreatments(): Treatment[] {
  const fallback: Treatment[] = [];
 
  const { data = fallback } = useQuery({
    queryKey: [queryKeys.treatments],
    queryFn: getTreatments,
  });
 
  return data;
}

위 코드와 같이 커스텀 훅을 사용하면 feature 별로 API에서 요청 받은 데이터를 독립적으로 컴포넌트 내에서 사용 할 수 있다


2. 쿼리 키 상수로 관리하기

쿼리를 사용할 때 마다 쿼리키를 직접 타이핑해서 사용하는 것 보다 상수로 관리 하는게 유지보수 측면에서 당연히 좋다.

export const queryKeys = {
  treatments: "treatments",
  appointments: "appointments",
  staff: "staff",
  user: "user",
};

Query Key를 상수로 관리하면 오타를 줄이고 재사용성과 유지보수성을 높일 수 있다.


3. useIsFetching 으로 전역 로딩 상태를 다뤄보자

먼저 useIsFetching은 현재 앱 내부에서 실행 중인 모든 쿼리 개수를 반환한다. 숫자가 0이면 현재 아무것도 fetching 중인 상태가 아니고 1이상이면 1개 이상의 데이터를 서버에 요청하는 훅이다.

function Loading() {
  const isFetching = useIsFetching();
  if (!isFetching) return null;
 
  return <Spinner />;
}

QueryClientProvider 안에 전역으로 사용할 로딩 컴포넌트를 배치하고 useIsFetching으로 로딩 상태 관리를 쉽게 할 수 있다.

// App.tsx 내부
<QueryClientProvider client={queryClient}>
  <AuthContextProvider>
    <Loading />
    <BrowserRouter>{/* 라우트 컴포넌트들 */}</BrowserRouter>
  </AuthContextProvider>
</QueryClientProvider>

전역 로딩 인디케이터를 한 번에 처리할 수 있어 사용자 경험을 향상시킬 수 있습니다.


4. 전역으로 성공/에러/로딩 처리하기 (QueryCache)

  • useQuery로 요청한 쿼리 결과와 상태 등을 캐싱한다.
  • 그리고 설정한 쿼리에 대해 성공,에러,로딩 상태를 추적해준다

먼저 에러 처리에 대해 예시를 들자면 아래 코드와 같이 공통으로 처리할 에러를 onError로 처리해 토스트를 사용할 수 있다.

function errorHandler(errorMsg: string) {
  const id = "react-query-toast";
  if (!toast.isActive(id)) {
    toast({
      id,
      title: `데이터를 불러올 수 없습니다: ${errorMsg}`,
      status: "error",
      variant: "subtle",
      isClosable: true,
    });
  }
}
 
export const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error) => {
      errorHandler(error.message);
    },
  }),
});

const id = "react-query-toast";는 에러가 발생했을때 토스트가 중복처리 되는 걸 막기 위함이다.


5. react-query-devtools로 디버깅 해주면 개발 할 때 query 상태를 추적할 수 있다.

import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
 
<ReactQueryDevtools initialIsOpen={false} />;