성공 데이터 타입을 보장하는 useQuery 커스텀 훅 작성하기

2023. 5. 26.
#TanStack Query

성공 데이터 타입을 보장하는 useQuery 커스텀 훅

최근 진행한 프로젝트에서 <Suspense /><ErrorBoundary />를 이용하여 일부 UI를 선언적으로 렌더링 하도록 구현했다.

타입스크립트를 사용한 프로젝트인데 <Suspense /><ErrorBoundary />로 컴포넌트를 감싸도 타입스크립트가 쿼리의 결과가 유효하다는 것을 추론할 수 있게끔 useQuery()의 상태가 항상 성공했는지 체크를 해주어야 했다.

const Contents = () => {
  const { isSuccess, data } = useQuery({ suspense: true });
  // 데이터를 받아오지 못했다면 fallback UI가 렌더링 될텐데 항상 성공 상태를 가정할 수는 없을까? 🤔
  if (!isSuccess) return null;
  return <Component {...data} />;
};

쿼리의 로딩과 에러 상태는 위에서 처리하고 있으니 타입만 수정을 수정하면 될 것이라고 생각해서 항상 suspense를 활성화하고 쿼리 데이터를 반환하는 커스텀 훅을 작성했다.

TanStack-QueryuseQuery 소스 코드를 보고 반환 타입을 재정의 했다.

import {
  parseQueryArgs,
  QueryFunction,
  QueryKey,
  useQuery,
  UseQueryOptions,
  UseQueryResult,
} from "@tanstack/react-query";

export type useQueryOptionWithOutSuspense<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey
> = Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, "suspense">;

const useQueryWithSuspense = <
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey
>(
  queryKey: TQueryKey,
  queryFunction?: QueryFunction<TQueryFnData, TQueryKey>,
  queryOptions?: useQueryOptionWithOutSuspense<
    TQueryFnData,
    TError,
    TData,
    TQueryKey
  >
) => {
  return useQuery({
    ...parseQueryArgs(queryKey, queryFunction, queryOptions),
    suspense: true,
  }) as UseQueryResult & {
    data: TData;
  };
};

export default useQueryWithSuspense;
const Contents = () => {
  const { data } = useQuery({ suspense: true });
  return <Component {...data} />;
};

Suspensive

데브코스 1기를 수료한 프롱이분은 이미 나와 비슷한 고민을 했고, 해결하기 위해 아예 라이브러리를 만들어버렸다.

suspensive.png

개발자가 Suspense를 편리하게 사용할 수 있도록 만들어진 라이브러리로, 클라이언트 사이드에서만 children을 렌더링하는 <Suspense.CSROnly /> 컴포넌트도 제공하고 있다.

단순히 리액트의 개념을 확장한 것으로 다른 패키지에 의존적이도 않고, 최근에는 TasStack-Query의 공식 문서에도 추가되었으니 비슷한 고민을 하고 있다면 사용해 보면 좋을 것 같다.