꿀모 프로젝트를 진행하면서 react-query에 대한 컨벤션 및 커스텀 훅에 대해 작업을 맡게 되었었는데, 이 작업을 진행하면서 공부하게 된 default-options 에 대해서 조금 더 상세하게 정리를 해보도록 한다.
회사 인턴을 하면서 지겹도록 리덕스 사가만 계속해서 만지고 있었는데 이번 기회에 react-query를 사용하게 되어서 반가운 기분이다 🤣
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)
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;
enabled
를 사용하면useQuery를 동기적으로 사용 가능합니다.
false로 설정하면 쿼리가 자동으로 호출되지 않습니다. 즉 최초 선언시의 호출을 막을 수 있습니다.
enabled 값이 true일때 useQuery를 실행합니다.
const { data: nextTodo, error, isFetching } = useQuery(
"nextTodos",
fetchNextTodoList,
{
enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
}
);
default: true
default: 0
default: false
default: false
default: true
default: true
default: true
default: false
['data', 'error']
, the component will only re-render when the data
or error
properties change.tracked
, access to properties will be tracked, and the component will only re-render when one of the tracked properties change.false
and suspense
is false
, errors are returned as state.true
) or return the error as state (false
).default: false
true
로 설정하면 status === 'loading'
일 때 쿼리가 일시 중단됩니다.status === 'error'
.default: false
먼저, retry 옵션은 API가 실패하면 설정한 값만큼 재시도 하는 옵션입니다. 저희 앱에서 API 요청 실패가 발생한다는 건, 서버의 문제가 있다고 판단하여 유저에게 빠른 응답을 보여주기 위해 0으로 설정하였습니다.
useErrorBoundary 옵션은 리액트16 이상에서 제공하는 Fallback UI 설정에 대한 옵션입니다. 예기치 못한 에러 케이스가 생길 수 있다고 판단하여 queries와 mutations에 true 값으로 설정하였습니다.
suspense를 (전역적으로) 사용하기 위해 옵션을 추가했습니다.
new QueryClient({
defaultOptions: {
queries: {
retry: 0,
suspense: true,
useErrorBoundary: true,
},
mutations: {
useErrorBoundary: true,
},
},
});
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만 다섯 번 재시도하지만 테스트의 모든 쿼리는 해제하라는 옵션을 여전히 가지고 있습니다 🙌.// 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,
});
}
좋은 글 감사합니다 😭👍👍👍