QueryClient의 Default Options 적용

이효범·2022년 7월 17일
18

React

목록 보기
3/5

꿀모 프로젝트를 진행하면서 react-query에 대한 컨벤션 및 커스텀 훅에 대해 작업을 맡게 되었었는데, 이 작업을 진행하면서 공부하게 된 default-options 에 대해서 조금 더 상세하게 정리를 해보도록 한다.

회사 인턴을 하면서 지겹도록 리덕스 사가만 계속해서 만지고 있었는데 이번 기회에 react-query를 사용하게 되어서 반가운 기분이다 🤣

QueryClient의 Default Options 적용

  • useQuery, useInfiniteQuery를 통한 쿼리 인스턴스는 기본적으로 캐시된 데이터를 오래된(stale) 데이터로 간주합니다.
    • 어떤 이유로든 전체 앱에 대해 동일한 쿼리 기능을 공유하고 쿼리 키를 사용하여 가져올 항목을 식별할 수 있기를 원하는 경우 React Query에 기본 쿼리 기능 을 제공하여 이를 수행할 수 있습니다.
      defaultOptions를 사용하여 앱에 기본 쿼리 함수를 제공합니다.

브라우저에서 사용자가 최신 데이터를 바라봐야 하는 상황은 ?

  • 근본적으로 화면을 보고 있을 때
  • 페이지가 전환 될 때 (새로운 페이지를 마주 했을 때)
  • 페이지 전환 없이 뭔가의 데이터를 요청할 때 (예를 들면 클릭 이벤트)

즉, 위 세 가지 경우를 제외하고는 데이터는 사용자 입장에서는 신선한(fresh) 상태가 아니어도 된다는 뜻입니다. 아래는 React-Query 가 기본적으로 제공하고 있는 옵션들 입니다.

refetchOnWindowFocus, //default: true
refetchOnMount, //default: true
refetchOnReconnect, //default: true

staleTime, //default: 0
cacheTime, //default: 5분 (60 * 5 * 1000)

즉, React-Query는 아래와 같은 경우 데이터를 Refetching 자동으로 다시 가져옵니다.

  • 쿼리의 새 인스턴스가 마운트 될 때 (refetchOnMount)
  • window 가 다시 포커스 될 때 (refetchOnWindowFocus)
  • 네트워크가 끊어졌다가 다시 연결될 때 (refetchOnReconnect)
  • 쿼리의 refetch interval 이 설정되어 있을 때 (refetchInterval)

refetchOnMount, refetchOnWindowFocus, refetchOnReconnect, refetchInterval 의 옵션들을 사용하여 위 기능들의 인터벌을 설정할 수 있습니다.

useQuery , useInfiniteQuery , query observer 의 활성 인스턴스가 더 이상 없을 경우 inactive 로 지정되고 나중에 다시 사용할 경우를 대비하여 캐시에 남아 있습니다.
기본적으로 inactive 쿼리는 5분 뒤에 가비지 콜렉팅 됩니다.

쿼리의 기본 cacheTime 을**1000 * 60 * 5 ms** 가 아닌 다른 값으로 변경할 수 있습니다.

실패한 쿼리를 캡쳐하여 UI 에 에러를 표시하기 전에 3회 자동으로 retry 합니다.

이를 변경하려면 쿼리에 대한 retry , retryDelay 옵션을 다른 값으로 설정해주면 됩니다.

기본적으로 쿼리 결과는 데이터가 실제로 변경 되었는지 감지하기 위해 공유되고, 변경되지 않은 경우 데이터 참조가 변경되지 않은 상태로 유지 됩니다.


enabled?: boolean;

staleTime?: number;

refetchInterval?: number | false | ((data: TData | undefined, query: Query<TQueryFnData, TError, TQueryData, TQueryKey>) => number | false);

refetchIntervalInBackground?: boolean;

refetchOnWindowFocus?: boolean | 'always' | ((query: Query<TQueryFnData, TError, TQueryData, TQueryKey>) => boolean | 'always');

refetchOnReconnect?: boolean | 'always' | ((query: Query<TQueryFnData, TError, TQueryData, TQueryKey>) => boolean | 'always');

refetchOnMount?: boolean | 'always' | ((query: Query<TQueryFnData, TError, TQueryData, TQueryKey>) => boolean | 'always');

retryOnMount?: boolean;

notifyOnChangeProps?: Array<keyof InfiniteQueryObserverResult> | 'tracked';

notifyOnChangePropsExclusions?: Array<keyof InfiniteQueryObserverResult>;

onSuccess?: (data: TData) => void;

onError?: (err: TError) => void;

onSettled?: (data: TData | undefined, error: TError | null) => void;

useErrorBoundary?: boolean | ((error: TError, query: Query<TQueryFnData, TError, TQueryData, TQueryKey>) => boolean);

select?: (data: TQueryData) => TData;

suspense?: boolean;

keepPreviousData?: boolean;

placeholderData?: TQueryData | PlaceholderDataFunction<TQueryData>;

optimisticResults?: boolean;
  • enable
    • enabled 를 사용하면useQuery를 동기적으로 사용 가능합니다.

    • false로 설정하면 쿼리가 자동으로 호출되지 않습니다. 즉 최초 선언시의 호출을 막을 수 있습니다.

    • enabled 값이 true일때 useQuery를 실행합니다.

      const { data: nextTodo, error, isFetching } = useQuery(
        "nextTodos",
        fetchNextTodoList,
        {
          enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
        }
      );
    • default: true

  • staleTime
    • 기본값은 0으로, 쿼리 데이터가 fresh에서 stale로 전환되는데 걸리는 시간입니다.
      • Infinity로 설정하면 쿼리 데이터는 직접 캐시를 무효화할 때까지 fresh 상태로 유지됩니다.
      • 캐시는 메모리에서 관리되므로 브라우저 새로고침 후에는 다시 가져옵니다.
      • default: 0
  • refetchInterval
    • 일정한 간격으로 refetching이 가능합니다.
    • 설정하면 쿼리가 이 빈도(밀리초)로 계속 refetching 합니다.
      • default: false
  • refetchIntervalInBackground
    • true로 설정하면 탭/창이 백그라운드에 있는 동안 쿼리가 계속 refetching 합니다.
      • default: false
  • refetchOnWindowFocus
    • 윈도우가 다시 포커스되었을 때 데이터를 호출할 것인지 여부입니다.
      • default: true
  • refetchOnReconnect
    • 네트워크가 끊어졌다가 다시 연결될 때 데이터를 호출할 것인지 여부입니다.
      • default: true
  • refetchOnMount
    • mount되었을 때 refetch 여부를 결정한다. true라면 stale 상태일 때 refetch 합니다.
      • default: true
  • retryOnMount
    • 'false'로 설정하면 쿼리에 오류가 포함된 경우 mount시, 쿼리가 재시도되지 않습니다.
      • default: false

  • notifyOnChangeProps
    • If set, the component will only re-render if any of the listed properties change.
    • When set to ['data', 'error'], the component will only re-render when the data or error properties change.
    • When set to tracked, access to properties will be tracked, and the component will only re-render when one of the tracked properties change.
  • notifyOnChangePropsExclusions
    • If set, the component will not re-render if any of the listed properties change.

  • onSuccess
    • queryFunction이 성공적으로 데이터를 가져왔을 때 호출되는 함수이다.
    • 이 콜백은 쿼리가 성공적으로 새 데이터를 가져오거나 'setQueryData'를 통해 캐시가 업데이트될 때마다 실행된다.
  • onError
    • queryFunction에서 오류가 발생했을 때 호출되는 함수이다.
    • 쿼리에 오류가 발생하고 오류가 전달되면 이 콜백이 실행됩니다.
  • onSettled
    • queryFunction이 성공, 실패한 경우 모두 실행되는 함수이다.

  • useErrorBoundary
    • suspense: true 혹은 useErrorBoundary: true 설정을 했을 경우, all errors will be thrown to the error boundary.
    • If set to false and suspense is false, errors are returned as state.
    • If set to a function, it will be passed the error and the query, and it should return a boolean indicating whether to show the error in an error boundary (true) or return the error as state (false).
      • default: false
  • select
    • 이 옵션을 사용하여 쿼리 함수에서 반환된 데이터의 일부를 변환하거나 선택할 수 있습니다.
  • suspense
    • Suspense
    • true로 설정하면 status === 'loading'일 때 쿼리가 일시 중단됩니다.
    • and throw errors when status === 'error'.
      • default: false
  • keepPreviousData
    • 새 쿼리 키를 기반으로 가져올 때 이전 '데이터'를 유지하려면 이를 'true'로 설정합니다.
    • queryKey가 변경되어 새로운 데이터를 요청하는 동안에도 마지막 data 값을 유지한다.
      • 페이지네이션을 구현할 때 유용함, 캐시되지 않은 페이지를 가져올 때 화면에서 viewing 컴포넌트가 사라지는 현상을 방지할 수 있다.
      • isPreviousData값으로 현재의 queryKey에 해당하는 값인지 확인할 수 있다.
  • placeholderData
    • 설정되면 이 값은 쿼리가 여전히 '로드 중' 데이터에 있고 initialData가 제공되지 않은 동안 이 특정 쿼리 관찰자의 자리 표시자 데이터로 사용됩니다.
  • optimisticResults
    • 설정된 경우 관찰자는 쿼리가 실제로 가져오기를 시작하기 전에 결과를 가져오는 상태로 낙관적으로 설정합니다.
      • 결과가 뒤처지지 않도록 하기 위함입니다.

  • cacheTime
    • 기본값은 5분으로, unused / inactive 캐시 데이터를 메모리에서 유지시킬 시간.
      • Infinity로 설정하면 쿼리 데이터가 캐시에서 제거되지 않습니다.
  • retry
    • 요청이 실패했을시 재요청을 실행합니다. 기본값은 3입니다.

그래서 어떤 옵션을 QueryClient의 Default Option으로 설정할까요?

  • 우선순위 1. QueryClient의 Default Option
    1. 먼저, retry 옵션은 API가 실패하면 설정한 값만큼 재시도 하는 옵션입니다. 저희 앱에서 API 요청 실패가 발생한다는 건, 서버의 문제가 있다고 판단하여 유저에게 빠른 응답을 보여주기 위해 0으로 설정하였습니다.

    2. useErrorBoundary 옵션은 리액트16 이상에서 제공하는 Fallback UI 설정에 대한 옵션입니다. 예기치 못한 에러 케이스가 생길 수 있다고 판단하여 queries와 mutations에 true 값으로 설정하였습니다.

    3. suspense를 (전역적으로) 사용하기 위해 옵션을 추가했습니다.

      new QueryClient({
        defaultOptions: {
          queries: {
            retry: 0,
      			suspense: true,
            useErrorBoundary: true,
          },
          mutations: {
            useErrorBoundary: true,
          },
        },
      });
    • 우선순위 2. setQueryDefaults 사용
      useQuery에서 직접 옵션을 설정하지 않고, 가능한 기본값을 사용하고 재정의 및 특정 쿼리에 대해 변경이 필요한 경우 queryClient.setQueryDefaults를 사용합니다.
      const queryClient = new QueryClient({
        defaultOptions: {
          queries: {
            retry: 2,
          },
        },
      })
      
      // ✅ only todos will retry 5 times
      queryClient.setQueryDefaults('todos', { retry: 5 })
      
      function App() {
        return (
          <QueryClientProvider client={queryClient}>
            <Example />
          </QueryClientProvider>
        )
      }
      모든 쿼리는 두 번 재시도하고, todos만 다섯 번 재시도하지만 테스트의 모든 쿼리는 해제하라는 옵션을 여전히 가지고 있습니다 🙌.
    • 우선순위 3. useQuery 사용
      useQuery 같은 경우는 각 특정 도메인 페이지에서 필요한 옵션들을 구체적으로 설정하여 사용하는 방향으로 합니다.
// src/pages/subscribe/index.jsx
import { useUserData } from '@/hooks/queries;
const { isLoading, data: kioskData } = useUserData({ storeCode, options: {
      onError:() => {...}
      onSuccess: () => {...},
			suspense: true,
});


//src/hooks/queries/useUserData.js
import { useQuery } from 'React Query';
import * as queryKeys from '@/constant/queryKeys';

export function useUserData({ storeCode, options }) {
  return useQuery([queryKeys.USER_DATA, storeCode], () => api.get(`...`), {
    select: ({ data }) => data?.userData,
    ...options,
  });
}
profile
I'm on Wave, I'm on the Vibe.

2개의 댓글

comment-user-thumbnail
2023년 10월 30일

좋은 글 감사합니다 😭👍👍👍

답글 달기
comment-user-thumbnail
2023년 10월 30일

좋은 글 감사합니다 😭👍👍👍

답글 달기