※ 해당 섹션은 3버전 react query로 4버전과 다른 부분이 있을 수 있습니다.
//useTreatment.ts
import { useQuery } from '@tanstack/react-query';
import type { Treatment } from '../../../../../shared/types';
import { axiosInstance } from '../../../axiosInstance';
import { queryKeys } from '../../../react-query/constants';
import { useCustomToast } from '../../app/hooks/useCustomToast';
async function getTreatments(): Promise<Treatment[]> {
const { data } = await axiosInstance.get('/treatments');
return data;
}
export function useTreatments(): Treatment[] {
const { data } = useQuery([queryKeys.treatments], getTreatments);
return data;
}
기존의 작업 방식에서는 isLoading, isError를 각각의 컴포넌트에서 진행했으나 custom hooks을 사용할 때는 중앙에서 처리할 수 있도록 설정해줍니다. 대신 다른 방법으로 처리를 합니다.
export function useTreatments(): Treatment[] {
const fallback = []; // fallback이 빈 배열이라고 설정. 서버에서 treatments 데이터를 받지 않고 캐시가 비어있는 경우 아무 것도 표시하지 않도록 해줍니다.
const { data = fallback } = useQuery([queryKeys.treatments], getTreatments);
return data;
}
isFetching
from useQuery
return object (useQuery 리턴 객체에서 isFetching 을 사용했습니다. useQuery 리턴 객체에서 isFetching의 구조를 분해했습니다.)isLoading
is isFetching
plus no cached data (isLoading은 isFetching의 캐시된 데이터가 없는 것과 같습니다. isFetching은 큰 항목, isLoading은 작은 항목으로 가져오기를 하면서 해당 쿼리에 대한 캐시된 데이터가 없는 경우입니다.)In a larger app
isFetching
(어떠한 쿼리가 데이터를 가져오는 중일 때 로딩 스피너를 표시하면 좋습니다. 앱 컴포넌트의 일부로서 중앙화된 로딩 스피너를 확보하도록 합니다. 쿼리가 가져오기 중인 경우 켜도록하고 가져오는 중인 쿼리가 없는 경우 끄도록 합니다.)useIsFetching
tells us this (useIsFetching은 현재 가져오기 중인 쿼리가 있는지를 우리에게 알려주는 훅입니다.)No need for isFetching on every custom hook / useQuery call (즉 각각의 커스텀 훅에 대해 isFetching을 사용할 필요가 없습니다. 대신 useIsFetching 훅을 로딩 컴포넌트에 사용할 수 있고 useIsFetching의 값은 스피너의 표시 여부를 우리에게 알려줄 것입니다.)
// app.tsx
...
export function App(): ReactElement {
return (
<ChakraProvider theme={theme}>
<QueryClientProvider client={queryClient}>
<Navbar />
<Loading /> // Loading 중일 때는 Loading한다는 표시를 나타낸다.
<Routes />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</ChakraProvider>
);
}
// Loading.tsx
import { Spinner, Text } from '@chakra-ui/react';
import { useIsFetching } from '@tanstack/react-query';
import { ReactElement } from 'react';
export function Loading(): ReactElement {
const isFetching = useIsFetching();
const display = isFetching ? 'inherit' : 'none';
return (
<Spinner
thickness="4px"
speed="0.65s"
emptyColor="olive.200"
color="olive.800"
role="status"
position="fixed"
zIndex="9999"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
display={display}
>
<Text display="none">Loading...</Text>
</Spinner>
);
}
useQuery
errors to Chakra UI “toast”onError
callback to useQuery
isError
, error
from useQuery
returnerror
parameter to callback// useTreatments.ts
...
export function useTreatments(): Treatment[] {
const toast = useCustomToast();
const fallback = [];
const { data = fallback } = useQuery([queryKeys.treatments], getTreatments, {
onError: (error) => {
const title =
error instanceof Error
? error.message
: 'error connecting to the server';
toast({ title, status: 'error' });
},
});
return data;
}
onError: (error: TError) => void
useError
analogy for useIsFetching
(useError 훅이 없는 이유: 정수 이상의 값이 반환되야 하니까 사용자에게 오류를 표시하려면 각 오류에 대한 문자열이 필요한데){
queries: { useQuery options },
mutations: { useMutation options }
}
// queryClient.ts
...
function queryErrorHandler(error: unknown): void {
const id = 'react-query-error';
const title =
error instanceof Error ? error.message : 'error connecting to server';
toast.closeAll();
toast({ id, title, status: 'error', variant: 'subtle', isClosable: true });
}
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: queryErrorHandler,
},
},
});
// useTreatments.ts
export function useTreatments(): Treatment[] {
const fallback = [];
const { data = fallback } = useQuery([queryKeys.treatments], getTreatments);
return data;
}
onError
: Error BoundaryuseErrorBoundary
for useQuery
useQuery
/ useMutation
defaultOptions
when creating QueryClient// useStaff.ts
import { useQuery } from '@tanstack/react-query';
import { Dispatch, SetStateAction, useState } from 'react';
import type { Staff } from '../../../../../shared/types';
import { axiosInstance } from '../../../axiosInstance';
import { queryKeys } from '../../../react-query/constants';
import { filterByTreatment } from '../utils';
async function getStaff(): Promise<Staff[]> {
const { data } = await axiosInstance.get('/staff');
return data;
}
interface UseStaff {
staff: Staff[];
filter: string;
setFilter: Dispatch<SetStateAction<string>>;
}
export function useStaff(): UseStaff {
const [filter, setFilter] = useState('all');
const fallback = []; // staff => fallback으로 이름 변경
const { data: staff = fallback } = useQuery([queryKeys.staff], getStaff); // 구조 분해 프로퍼티의 이름을 data에서 staff 로 바꾼다.
// 쿼리 키는 queryKeys 상수의 staff 프로퍼티이며 쿼리 함수는 getStaff 입니다.
return { staff, filter, setFilter }; // 반환 객체에 staff를 반환할 수 있도록 만든다.
}
useIsFetching
(이 값을 이용해 로딩스피너의 디스플레이 속성을 켰다 껐다 할 수 있습니다.)onError
callback (useQuery 호출에서 분해하는 isError와 error에 의존하지 않아도 됩니다. useTreatment 커스텀 훅 내 useQuery에 onError에 대한 콜백 옵션을 만든 다음 집중화 시키고 QueryClient 옵션 기본 값으로 만들었습니다. 그러면 useQuery를 호출 시 다른 옵션으로 덮어쓰지 않는 이상 모든 useQuery가 해당 OnError 콜백을 사용합니다.)reference
https://www.udemy.com/course/learn-react-query
https://tanstack.com/query/v4/docs/devtools?from=reactQueryV3&original=https://react-query-v3.tanstack.com/devtools
https://create-react-app.dev/docs/adding-custom-environment-variables
https://chakra-ui.com/docs/components/toast
https://tanstack.com/query/v4/docs/reference/useQuery?from=reactQueryV3&original=https://react-query-v3.tanstack.com/reference/useQuery
https://reactjs.org/docs/error-boundaries.html