react-query-tutorial
react-query-tutorial copied to clipboard
๐ TanStack Query(aka. react query) ์์ ์์ฃผ ์ฌ์ฉ๋๋ ๊ฐ๋ ์ ๋ฆฌ
๐ป React-query
- ํด๋น ์ ์ฅ์๋
react-query์์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ ๋ค์ ์ ๋ฆฌํ ์ ์ฅ์์ ๋๋ค. react-query์ ๋ชจ๋ ํ์ฉ ๋ฐฉ๋ฒ์ด ์์ฑ๋ ์ํ๋ ์๋๋ฉฐ, ์ถ๊ฐ๊ฐ ํ์ํ ๋ด์ฉ์ ์ถ๊ฐ, ๋ณด์ํ ์์ ์ ๋๋ค. - ์คํ์ ๋ฐ ๊ฐ๋
์ฑ์ด ์์ข๊ฑฐ๋ ์์ ์ด ํ์ํ ๋ด์ฉ์
Pull Request,Issue๋ฑ ์์ ๋กญ๊ฒ ๋จ๊ฒจ์ฃผ์๋ฉด ๊ฒํ ํ์ ๋ฐ์ํ๊ฒ ์ต๋๋ค. ๋ง๊ด๋ถ ๐โโ๏ธ - react-query์ ์์ธํ ํ์ฉ๋ฒ์ ๊ณต์ ์ฌ์ดํธ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์๊ธธ ๋ฐ๋๋๋ค.
react-query v4 ์ ์ ๋ฆด๋ฆฌ์ฆ

- react-query v4๊ฐ ์ ์ ๋ฆด๋ฆฌ์ฆ๋๋ฉด์ ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ์ ์๋ ๋ฌธ์์ ์ถ๊ฐํ๊ณ ์์ต๋๋ค.
- react-query v3 vs v4 ๋น๊ต
๐ ๋ชฉ์ฐจ
- React-Query ๊ธฐ๋ฅ
- ๊ธฐ๋ณธ ์ค์ (QueryClientProvider, QueryClient)
- React Query Devtools
- React Query ์บ์ฑ ๋ผ์ดํ ์ฌ์ดํด
- useQuery
- useQuery ์ฃผ์ ๋ฆฌํด ๋ฐ์ดํฐ
- staleTime๊ณผ cacheTime
- ๋ง์ดํธ ๋ ๋๋ง๋ค ์ฌ์์ฒญํ๋ refetchOnMount
- ์๋์ฐ ํฌ์ปค์ฑ ๋ ๋ ์ฌ ์์ฒญํ๋ refetchOnWindowFocus
- Polling ๋ฐฉ์์ ๊ตฌํํ๊ธฐ ์ํ refetchInterval์ refetchIntervalInBackground)
- ์๋ ์คํ์ enabled์ ์๋์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ๋ค์ ์์ฒญํ๋ refetch
- ์คํจ ์ฟผ๋ฆฌ์ ๋ํ ์ฌ ์์ฒญํ๋ retry
- onSuccess, onError, onSettled Callback
- select๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ๋ณํ
- Paginated ๊ตฌํ์ ์ ์ฉํ keepPreviousData
- ์ฟผ๋ฆฌ๋ฅผ ๋ณ๋ ฌ(Parallel) ์์ฒญํ ์ ์๋ useQueries
- ์ข ์ ์ฟผ๋ฆฌ(Dependent Queries)
- QueryClient ์ธ์คํด์ค๋ฅผ ๋ฐํํ๋ useQueryClient
- ์ด๊ธฐ ๋ฐ์ดํฐ ์ค์ ํ ์ ์๋ initialData
- Infinite Queries
- ์๋ฒ์ Http CUD๊ด๋ จ ์์ ์ ์ํ useMutation๊ณผ mutate
- ์ฟผ๋ฆฌ ๋ฌดํจํ queryClient.invalidateQueries
- ์บ์ ๋ฐ์ดํฐ ์ฆ์ ์ ๋ฐ์ดํธ๋ฅผ ์ํ queryClient.setQueryData
- ์ฌ์ฉ์ ๊ฒฝํ(UX) ์ฌ๋ ค์ฃผ๋ Optimistic Updates(๋๊ด์ ์ ๋ฐ์ดํธ)
- ์๋ฌ ์ฒ๋ฆฌ useQueryErrorResetBoundary
๐ React-Query ๊ฐ์ ๋ฐ ๊ธฐ๋ฅ
๊ฐ์
- react-query๋ ๋ฆฌ์กํธ ์ ํ๋ฆฌ์ผ์ด์
์์
์๋ฒ ์ํ ๊ฐ์ ธ์ค๊ธฐ,์บ์ฑ,๋๊ธฐํ ๋ฐ ์ ๋ฐ์ดํธ๋ฅผ ๋ณด๋ค ์ฝ๊ฒ ๋ค๋ฃฐ ์ ์๋๋ก ๋์์ฃผ๋ฉฐ ํด๋ผ์ด์ธํธ ์ํ์ ์๋ฒ ์ํ๋ฅผ ๋ช ํํ ๊ตฌ๋ถํ๊ธฐ ์ํด์ ๋ง๋ค์ด์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. - react-query์์ ๊ธฐ์กด ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
(redux, mobX)๋ํด๋ผ์ด์ธํธ ์ํ ์์์ ์ ํฉํ์ง๋ง๋น๋๊ธฐ ๋๋ ์๋ฒ ์ํ ์์์๋ ๊ทธ๋ค์ง ์ข์ง ์๋ค๊ณ ๋งํ๊ณ ์๋ค. - ํด๋ผ์ด์ธํธ ์ํ(Client State) ์ ์๋ฒ ์ํ(Server State)๋ ์์ ํ ๋ค๋ฅด๋ฉฐ, ํด๋ผ์ด์ธํธ ์ํ๋ ์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํ๋ ๊ฐ๊ฐ์ input ๊ฐ์ผ๋ก ์๋ฅผ ๋ค ์ ์๊ณ ์๋ฒ ์ํ๋ database์ ์ ์ฅ๋์ด์๋ ๋ฐ์ดํฐ๋ก ์๋ฅผ ๋ค ์ ์๋ค.
๊ธฐ๋ฅ
-
์๋
- React Query์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์์น์ ๋ฐ์ดํฐ๊ฐ ์ผ๋ง๋ ํ์ํ์ง ์๋ ค์ฃผ๋ฉด ๋๋จธ์ง๋ ์๋์ด๋ค. React Query๋ ๊ตฌ์ฑ์ด ํ์ ์๋ ์ฆ์
์บ์ฑ,๋ฐฑ๊ทธ๋ผ์ด๋ ์ ๋ฐ์ดํธ๋ฐ ์ค๋๋ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
- React Query์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์์น์ ๋ฐ์ดํฐ๊ฐ ์ผ๋ง๋ ํ์ํ์ง ์๋ ค์ฃผ๋ฉด ๋๋จธ์ง๋ ์๋์ด๋ค. React Query๋ ๊ตฌ์ฑ์ด ํ์ ์๋ ์ฆ์
-
์น์ํ๊ณ ๊ฐ๋จํ๋ค.
promise๋๋async/await๋ก ์์ ํ๋ ๋ฐฉ๋ฒ์ ์๊ณ ์๋ค๋ฉด ์ด๋ฏธ React Query๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์๊ณ ์๋ ๊ฒ์ด๋ค. ๊ด๋ฆฌํ ์ ์ญ ์ํ, ๊ฐ์๊ธฐ, ์ ๊ทํ ์์คํ ๋๋ ์ดํดํด์ผ ํ ๋ฌด๊ฑฐ์ด ๊ตฌ์ฑ์ด ์๋ค. ๋ฐ์ดํฐ๋ฅผ ํด๊ฒฐํ๋(๋๋ ์ค๋ฅ๋ฅผ ๋ฐ์์ํค๋) ํจ์๋ฅผ ์ ๋ฌํ๊ธฐ๋ง ํ๋ฉด๋๋ค.
-
๊ฐ๋ ฅํ ๋๊ตฌ ๋ฐ ๊ตฌ์ฑ
- React Query๋ ๋ชจ๋ ์ฌ์ฉ ์ฌ๋ก์ ๋ง๋ ๋
ธ๋ธ์ ์ต์
์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ์ ๊ฐ ๊ด์ฐฐ์ ์ธ์คํด์ค๊น์ง ๊ตฌ์ฑํ ์ ์๋ค.
์ ์ฉ devtools,๋ฌดํ ๋ก๋ฉ API๋ฐ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ๋ฅผ ์ฝ๊ฒ ๋ง๋ค์ด์ฃผ๋ mutation ๋๊ตฌ๊ฐ ์๋ค.
- React Query๋ ๋ชจ๋ ์ฌ์ฉ ์ฌ๋ก์ ๋ง๋ ๋
ธ๋ธ์ ์ต์
์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ์ ๊ฐ ๊ด์ฐฐ์ ์ธ์คํด์ค๊น์ง ๊ตฌ์ฑํ ์ ์๋ค.
-
์ ์ ์ฝ๋. ๋ ์ ์ ์ฃ์ง ์ผ์ด์ค.
๋ฆฌ๋์,์บ์ฑ ๋ก์ง,ํ์ด๋จธ,์ฌ์ฌ์ฉ ๋ก์ง,๋ณต์กํ ๋น๋๊ธฐ/๋๊ธฐ ์คํฌ๋ฆฝํ์ ํ์ ํ๋ ์ฝ๋๋ณด๋ค ์ ์ ์์ ์ฝ๋๋ก ์์ฑํ ์ ์๋ค.
React-Query ๊ธฐ๋ณธ ์ค์
// QueryClient ์์
import { QueryClient } from "react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
});
- QueryClient๋ฅผ ์ฌ์ฉํ์ฌ
์บ์์ ์ํธ ์์ฉํ ์ ์์ต๋๋ค. - QueryClient์์
๋ชจ๋ query๋๋mutation์ ๊ธฐ๋ณธ ์ต์ ๋ค์ ์ถ๊ฐํ ์ ์๋๋ฐ ์ข ๋ฅ๊ฐ ์๋นํ๋ ์๋จ์ ๊ณต์ ์ฌ์ดํธ๋ฅผ ์ฐธ๊ณ ํ์.
// QueryClientProvider + QueryClient
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div>๋ธ๋ผ๋ธ๋ผ</div>
</QueryClientProvider>;
);
}
- App ์ ์ฒด์์ ๋ฆฌ์กํธ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋
QueryClientProvider๋ฅผ ์ต์๋จ์์ ๊ฐ์ธ์ฃผ๊ณQueryClient๋ฅผ Props๋ก ๋ฃ์ด์ค์ผ ํ๋ค. - App.js์ QueryClientProvider๋ก ์ดํ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ๊ณ QueryClient๋ฅผ ๋ด๋ ค๋ณด๋ด์ค โ ์ด context๋ ์ฑ์์ ๋น๋๊ธฐ ์์ฒญ์ ์์์ ์ฒ๋ฆฌํ๋
background๊ณ์ธต์ด๋๋ค. - QueryClientProvider ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ App์ QueryClient๋ฅผ ์ฐ๊ฒฐํ๊ณ ์ ๊ณตํ๋ค.
Devtools

- React Query๋
์ ์ฉ devtools๋ฅผ ์ ๊ณตํ๋ค. - devtools๋ฅผ ์ฌ์ฉํ๋ฉด React Query์ ๋ชจ๋ ๋ด๋ถ ๋์์
์๊ฐํํ๋๋ฐ ๋์์ด ๋๋ฉฐ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด๋๋ฒ๊น ์๊ฐ์ ์ ์ฝํ ์ ์๋ค.
import { ReactQueryDevtools } from "react-query/devtools";
<AppContext.Provider value={user}>
<QueryClientProvider client={queryClient}>
// ...
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
</QueryClientProvider>
</AppContext.Provider>;
-
initialIsOpen (Boolean)
true์ด๋ฉด ๊ฐ๋ฐ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด๋ ค ์๋๋ก ์ค์ ํ ์ ์๋ค.
-
position?: ("top-left" | "top-right" | "bottom-left" | "bottom-right")
- ๊ธฐ๋ณธ๊ฐ:
bottom-left - devtools ํจ๋์ ์ด๊ณ ๋ซ๊ธฐ ์ํ ๋ก๊ณ ์์น
- ๊ธฐ๋ณธ๊ฐ:
-
์ผ๋ฐ์ ์ผ๋ก initialIsOpen, position์ ์์ฃผ ์ฌ์ฉํ์ง๋ง, ์๋์ ๊ฐ์ ์ต์ ๋ค๋ ์กด์ฌํ๋ค.
-
panelProps
- ํจ๋์
props์ ์ถ๊ฐํ ์ ์๋ค. ์๋ฅผ ๋ค์ด className, style, onClick ๋ฑ
- ํจ๋์
-
closeButtonProps
๋ซ๊ธฐ ๋ฒํผ์ props๋ฅผ ์ถ๊ฐํ ์ ์๋ค.
-
toggleButtonProps
ํ ๊ธ ๋ฒํผ์ props๋ฅผ ์ถ๊ฐํ ์ ์๋ค.
-
Devtools๋ ๊ธฐ๋ณธ๊ฐ์ผ๋ก
process.env.NODE_ENV === 'development'์ธ ๊ฒฝ์ฐ์๋ง ์คํ๋๋ค, ์ฆ ์ผ๋ฐ์ ์ผ๋ก ๊ฐ๋ฐํ๊ฒฝ์์๋ง ์๋ํ๋ฏ๋ก ์ค์ ๋์ด์์ผ๋ฏ๋ก ํ๋ก์ ํธ ๋ฐฐํฌ์์ Devtools ์ฝ์ ์ฝ๋๋ฅผ ์ ๊ฑฐํด์ค ํ์๊ฐ ์๋ค.
์บ์ฑ ๋ผ์ดํ ์ฌ์ดํด
- React-Query ์บ์ ๋ผ์ดํ ์ฌ์ดํด
* Query Instances with and without cache data(์บ์ ๋ฐ์ดํฐ๊ฐ ์๊ฑฐ๋ ์๋ ์ฟผ๋ฆฌ ์ธ์คํด์ค)
* Background Refetching(๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํจ์นญ)
* Inactive Queries(๋นํ์ฑ ์ฟผ๋ฆฌ)
* Garbage Collection(๊ฐ๋น์ง ์ปฌ๋ ์
)
- cacheTime์ ๊ธฐ๋ณธ๊ฐ 5๋ถ, staleTime ๊ธฐ๋ณธ ๊ฐ 0๋ถ์ ๊ฐ์
A๋ผ๋ queryKey๋ฅผ ๊ฐ์ง A ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐmount๋จ- ๋คํธ์ํฌ์์ ๋ฐ์ดํฐ fetch ํ๊ณ , ๋ถ๋ฌ์จ ๋ฐ์ดํฐ๋ A๋ผ๋ queryKey๋ก
์บ์ฑํจ - ์ด ๋ฐ์ดํฐ๋
fresh์ํ์์staleTime(๊ธฐ๋ณธ๊ฐ 0)์ดํstale์ํ๋ก ๋ณ๊ฒฝ๋จ - A ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ
unmount๋จ - ์บ์๋
cacheTime(๊ธฐ๋ณธ๊ฐ 5min)๋งํผ ์ ์ง๋๋ค๊ฐ๊ฐ๋น์ง ์ฝ๋ ํฐ(GC)๋ก ์์ง๋จ - ๋ง์ผ, cacheTime์ด ์ง๋๊ธฐ ์ ์ด๊ณ , A ์ฟผ๋ฆฌ ์ธ์คํด์ค freshํ ์ํ๋ผ๋ฉด ์๋กญ๊ฒ mount๋๋ฉด ์บ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค๋ค.
useQuery
useQuery ๊ธฐ๋ณธ ๋ฌธ๋ฒ
// ์ฌ์ฉ๋ฒ(1)
const { data, isLoading, ... } = useQuery(queryKey, queryFn, {
//์ต์
๋ค ex) enabled, staleTime
});
// ์ฌ์ฉ๋ฒ(2)
const result = useQuery({
queryKey,
queryFn,
//์ต์
๋ค ex) enabled, staleTime
});
// ์ค์ ์์
const getSuperHero = useCallback(() => {
return axios.get("http://localhost:4000/superheroes");
}, []);
const { isLoading, data } = useQuery("super-heroes", getSuperHero);
- useQuery๋ ๊ธฐ๋ณธ์ ์ผ๋ก 3๊ฐ์ ์ธ์๋ฅผ ๋ฐ์ต๋๋ค. ์ฒซ ๋ฒ์งธ ์ธ์๊ฐ
queryKey(ํ์), ๋ ๋ฒ์งธ ์ธ์๊ฐqueryFn(ํ์), ์ธ ๋ฒ์งธ ์ธ์๊ฐoptions์ ๋๋ค. - useQuery๋ ์ฒซ ๋ฒ์งธ ์ธ์์ธ
queryKey๋ฅผ ๊ธฐ๋ฐ์ผ๋ก๋ฐ์ดํฐ ์บ์ฑ์ ๊ด๋ฆฌํฉ๋๋ค.๋ฌธ์์ด๋๋๋ฐฐ์ด๋ก ์ง์ ํ ์ ์๋๋ฐ, ์ผ๋ฐ์ ์ผ๋ก๋ ์ ์์ ์ฒ๋ผ๋ฌธ์์ด๋ก ์ง์ ํ ์ ์์ง๋ง, ๋ง์ฝ ์ฟผ๋ฆฌ๊ฐ ํน์ ๋ณ์์ ์์กดํ๋ ๊ฒฝ์ฐ์๋ ์๋ ์์ ์ฒ๋ผ๋ฐฐ์ด๋ก ์ง์ ํด ํด๋น ๋ณ์๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํฉ๋๋ค. ์ฌ์ฉ๋ฒ (1)๊ณผ(2)๋ฒ ๋๋ค ์ฌ์ฉ๋๋๋ฐ. ์ ๊ทผ ๋ฐฉ์์ ์ฐจ์ด์ ๋๋ค. ๋ ๊ฐ์ง ๋ฐฉ์ ๋ชจ๋ ์ ์ดํดํ๊ณ ์ฌ์ฉํฉ์๋ค.
// (1)
const fetchSuperHero = ({ queryKey }: any) => {
const heroId = queryKey[1]; // queryKey: (2) ['super-hero', '3']
return axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
const useSuperHeroData = (heroId: string) => {
return useQuery(["super-hero", heroId], fetchSuperHero);
};
// (2)
const fetchSuperHero = (heroId: string) => {
return axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
const useSuperHeroData = (heroId: string) => {
return useQuery(["super-hero", heroId], () => fetchSuperHero(heroId));
};
- useQuery์ ๋ ๋ฒ์งธ ์ธ์์ธ queryFn๋
Promise๋ฅผ ๋ฐํํ๋ ํจ์๋ฅผ ๋ฃ์ด์ฃผ์ด์ผ ํฉ๋๋ค. - useQuery์ ์ธ ๋ฒ์งธ ์ธ์์ธ
options์ ๋ง์ด ์ฐ์ด๋ ์ต์ ๋ค์ ๋ฐ์์ ์ค๋ช ํ์์ต๋๋ค. ๊ทธ์ธ์๋ ์์ useQuery ์ฐธ๊ณ ์ฌ์ดํธ๋ฅผ ํตํด ํ์ธํ๋ฉด๋๋ค.
- ์ฐธ๊ณ ๋ก ๋์ค์ queryClient๋ก ํน์ key์ ํด๋นํ๋ query์ ์ ๊ทผํ ๋๋ ์ด๊ธฐ์ ์ค์ ํด๋ ํฌ๋งท์ ์ง์ผ์ค์ผ ์ ๋๋ก ๊ฐ์ ธ์ฌ ์ ์๋ค.
- ์๋ ์์ ๋ฅผ ์ฐธ๊ณ ํ๋ฉด useQuery์์ queryKey์ ํด๋นํ๋ ํฌ๋งท์ด ๋ฐฐ์ด
["super-hero", heroId]์ด๋ค. ๊ทธ๋ ๋ค๋ฉด ๋ฐ์ useMutation์์ setQueryData๋ฅผ ์ด์ฉํ ๋ ๋๊ฐ์ด["super-hero", heroId]ํฌ๋งท์ ๊ฐ์ ธ์ผํ๋ค.
// ์
const useSuperHeroData = (heroId: string) => {
return useQuery(["super-hero", heroId], () => fetchSuperHero(heroId));
};
const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
onSuccess(data) {
// ์ ๋๋ก ๋ชป๊ฐ์ ธ์ด! ํฌ๋งท์๋ง์! ["super-hero", heroId] ๋กํด์ผ๋
queryClient.setQueryData("super-hero", (oldData: any) => {
return {
...oldData,
data: [...oldData.data, data.data],
};
});
},
onError(err) {
console.log(err);
},
});
};
useQuery ์ฃผ์ ๋ฆฌํด ๋ฐ์ดํฐ
const { isLoading, isError, error, data, isFetching } = useQuery(
["colors", pageNum],
() => fetchColors(pageNum)
);
- react-query: useQuery ๊ณต์ ์ฌ์ดํธ
- status: ์ฟผ๋ฆฌ ์์ฒญ ํจ์์ ์ํ๋ฅผ ํํํ๋ status๋ 4๊ฐ์ง์ ๊ฐ์ด ์กด์ฌํ๋ค.(๋ฌธ์์ด ํํ)
- idle: ์ฟผ๋ฆฌ ๋ฐ์ดํฐ๊ฐ ์๊ณ ๋น์์ ๋, { enabled: false } ์ํ๋ก ์ฟผ๋ฆฌ๊ฐ ํธ์ถ๋๋ฉด ์ด ์ํ๋ก ์์๋๋ค.
- loading: ๋ง ๊ทธ๋๋ก ๋ก๋ฉ์ค์ผ ๋ ์ํ
- error: ์๋ฌ ๋ฐ์ํ์ ๋ ์ํ
- success: ์์ฒญ ์ฑ๊ณตํ์ ๋ ์ํ
- data: ์ฟผ๋ฆฌ ํจ์๊ฐ ๋ฆฌํดํ Promise์์
resolved๋ ๋ฐ์ดํฐ - isLoading:
์บ์ฑ๋ ๋ฐ์ดํฐ๊ฐ ์์๋!fetch ๊ณผ์ ์ค์ true ์ฆ, ์บ์ฑ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด false - isFetching: ๋ฐ์ดํฐ๊ฐ
fetch๋ ๋ false์์ true๊ฐ ๋๋ค. ์บ์ฑ ๋ฐ์ดํฐ๊ฐ ์์ด์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ fetch ๋๋๋ผ๋ true์ด๋ค. - error: ์ฟผ๋ฆฌ ํจ์๊ฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ์ ๋ํ ์ค๋ฅ ๊ฐ์ฒด
- isError: ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ
true - ๊ทธ์ธ ๋ฆฌํด ๋ฐ์ดํฐ๋ค์ ํ์ธํ๊ณ ์ถ์ผ๋ฉด ๊ณต์ ์ฌ์ดํธ ์ฐธ๊ณ
useQuery ์ฃผ์ Options
- useQuery ๊ณต์ ์ฌ์ดํธ ์ฐธ๊ณ
- ์๋ ์์ ๋ค ์ ์ธํ๊ณ ์ถ๊ฐ์ ์ธ ์ต์ ๋ค์ ์ ์ฌ์ดํธ ์ฐธ๊ณ
staleTime cacheTime
- stale์ ์ฉ์ด ๋ป๋๋ก
์ฉ์์ด๋ผ๋ ์๋ฏธ์ด๋ค. ์ฆ, ์ต์ ์ํ๊ฐ ์๋๋ผ๋ ์๋ฏธ์ด๋ค. - fresh๋ ๋ป ๊ทธ๋๋ก
์ ์ ํ์ด๋ผ๋ ์๋ฏธ์ด๋ค. ์ฆ, ์ต์ ์ํ๋ผ๋ ์๋ฏธ์ด๋ค.
const { isLoading, isFetching, data, isError, error } = useQuery(
"super-heroes",
getSuperHero,
{
cacheTime: 3000,
staleTime: 50000,
}
);
- staleTime (number | Infinity)
- staleTime์ ๋ฐ์ดํฐ๊ฐ
fresh์์ stale์ํ๋ก ๋ณ๊ฒฝ๋๋๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ, ๋ง์ฝ staleTime์ด 3000์ด๋ฉด fresh์ํ์์ 3์ด๋ค์ stale๋ก ๋ณํ fresh์ํ์ผ๋๋ ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ ์๋กญ๊ฒ mount ๋์ด๋ ๋คํธ์ํฌ ์์ฒญ(fetch)์ด ์ผ์ด๋์ง ์๋๋ค.- ๋ฐ์ดํฐ๊ฐ ํ๋ฒ fetch ๋๊ณ ๋์
staleTime์ด ์ง๋์ง ์์๋ค๋ฉด(fresh์ํ) unmount ํ ๋ค์ mount ๋์ด๋ fetch๊ฐ ์ผ์ด๋์ง ์๋๋ค. - staleTime์ ๊ธฐ๋ณธ๊ฐ์
0์ด๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ผ๋ก fetch ํ์ ๋ฐ๋ก stale์ด ๋๋ค.
- staleTime์ ๋ฐ์ดํฐ๊ฐ
- cacheTime (number | Infinity)
- ๋ฐ์ดํฐ๊ฐ
inactive์ํ์ผ ๋์บ์ฑ๋ ์ํ๋ก๋จ์์๋ ์๊ฐ - ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ unmount ๋๋ฉด ๋ฐ์ดํฐ๋
inactive ์ํ๋ก ๋ณ๊ฒฝ๋๋ฉฐ, ์บ์๋cacheTime๋งํผ ์ ์ง๋๋ค. - cacheTime์ด ์ง๋๋ฉด
๊ฐ๋น์ง ์ฝ๋ ํฐ๋ก ์์ง๋๋ค. - cacheTime์ด ์ง๋๊ธฐ ์ ์ ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ ๋ค์ mount ๋๋ฉด, ๋ฐ์ดํฐ๋ฅผ fetchํ๋ ๋์ ์บ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค๋ค.
- cacheTime์ staleTime๊ณผ ๊ด๊ณ์์ด, ๋ฌด์กฐ๊ฑด inactive ๋ ์์ ์ ๊ธฐ์ค์ผ๋ก ์บ์ ๋ฐ์ดํฐ ์ญ์ ๋ฅผ ๊ฒฐ์ ํ๋ค.
- cacheTime์ ๊ธฐ๋ณธ๊ฐ์
5๋ถ์ด๋ค.
- ๋ฐ์ดํฐ๊ฐ
- ์ฌ๊ธฐ์ ์ฃผ์ํ ์ ์ staleTime๊ณผ cacheTime์ ๊ธฐ๋ณธ๊ฐ์ ๊ฐ๊ฐ
0๋ถ๊ณผ5๋ถ์ด๋ค. ๋ฐ๋ผ์ staleTime์ ์ด๋ ํ ์ค์ ๋ ํ์ง ์์ผ๋ฉด ์บ์ฑ์ด ์ ํ๋์ง ์๋๋ค. ์๋ํ๋ฉด ํญ์ ์บ์ฑ๋์ด ์๋ ๋ฐ์ดํฐ๊ฐstaleํ๋ค๊ณ ์ฌ๊ธฐ๊ธฐ ๋๋ฌธ์ด๋ค. - staleTime์ ๊ธธ๊ฒ ์ค์ ํ๋๋ผ๋ cacheTime์ด ์งง๋ค๋ฉด ์ด ๋ํ ์บ์ฑ์ด ์ํํ๊ฒ ์งํ๋์ง ์์ ๊ฒ์ด๋ค. ๊ฒฐ๊ตญ์๋ ๋๊ฐ์ ์ต์ ์ ์ ์ ํ๊ฒ ์ค์ ํด์ค์ผ ํ๋ค.
refetchOnMount
const { isLoading, isFetching, data, isError, error } = useQuery(
"super-heroes",
getSuperHero,
{
refetchOnMount: true,
}
);
- refetchOnMount (boolean | "always")
- refetchOnMount๋ ๋ฐ์ดํฐ๊ฐ
stale์ํ์ผ ๊ฒฝ์ฐ mount ๋ง๋คrefetch๋ฅผ ์คํํ๋ ์ต์ ์ด๋ค. ๊ธฐ๋ณธ๊ฐ์true์ด๋ค. always๋ก ์ค์ ํ๋ฉด ๋ง์ดํธ ์ ๋ง๋ค ๋งค๋ฒ refetch๋ฅผ ์คํํ๋ค.false๋ก ์ค์ ํ๋ฉด ์ต์ด fetch ์ดํ์๋ refetchํ์ง ์๋๋ค.
refetchOnWindowFocus
const { isLoading, isFetching, data, isError, error } = useQuery(
"super-heroes",
getSuperHero,
{
refetchOnWindowFocus: true,
}
);
- refetchOnWindowFocus (boolean | "always")
- refetchOnWindowFocus๋ ๋ฐ์ดํฐ๊ฐ
stale์ํ์ผ ๊ฒฝ์ฐ์๋์ฐ ํฌ์ปค์ฑ๋ ๋ ๋ง๋ค refetch๋ฅผ ์คํํ๋ ์ต์ ์ด๋ค. ๊ธฐ๋ณธ๊ฐ์true์ด๋ค. - ์๋ฅผ ๋ค์ด, ํฌ๋กฌ์์ ๋ค๋ฅธ ํญ์ ๋๋ ๋ค๊ฐ ๋ค์ ์๋ ๋ณด๋ ์ค์ธ ํญ์ ๋๋ ์ ๋๋ ์ด ๊ฒฝ์ฐ์ ํด๋นํ๋ค. ์ฌ์ง์ด F12๋ก ๊ฐ๋ฐ์ ๋๊ตฌ ์ฐฝ์ ์ผ์ ๋คํธ์ํฌ ํญ์ด๋ , ์ฝ์ ํญ์ด๋ ๊ฐ๋ฐ์ ๋๊ตฌ ์ฐฝ์์ ๋๋ค๊ฐ ํ์ด์ง ๋ด๋ถ๋ฅผ ๋ค์ ํด๋ฆญํ์ ๋๋ ์ด ๊ฒฝ์ฐ์ ํด๋นํ๋ค.
always๋ก ์ค์ ํ๋ฉด ํญ์ ์๋์ฐ ํฌ์ปค์ฑ ๋ ๋ ๋ง๋ค refetch๋ฅผ ์คํํ๋ค๋ ์๋ฏธ์ด๋ค.
Polling
const { isLoading, isFetching, data, isError, error } = useQuery(
"super-heroes",
getSuperHero,
{
// refetchInterval: 2000,
refetchIntervalInBackground: true,
}
);
- ํด๋ง์ด๋? ๋ฆฌ์ผํ์ ์น์ ์ํ ๊ธฐ๋ฒ์ผ๋ก
์ผ์ ํ ์ฃผ๊ธฐ(ํน์ ํ ์๊ฐ)๋ฅผ ๊ฐ์ง๊ณ ์๋ฒ์ ์๋ต์ ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ์์ด ํด๋ง ๋ฐฉ์์ด๋ค. - react-query์์๋
refetchInterval์ ์ด์ฉํด์ ๊ตฌํํ ์ ์๋ค. refetchIntervalInBackground์ผ๋ก๋ ํด๋ง์ ๊ตฌํํ ์ ์๋๋ฐrefetchIntervalํญ/์ฐฝ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์ ์๋ ๋์ ๊ณ์ ๋ค์ ๊ฐ์ ธ์ต๋๋ค.
enabled refetch
const { isLoading, isFetching, data, isError, error, refetch } = useQuery(
"super-heroes",
getSuperHero,
{
enabled: false,
}
);
const handleClickRefetch = useCallback(() => {
refetch();
}, [refetch]);
return (
<div>
{data?.data.map((hero: Data) => (
<div key={hero.id}>{hero.name}</div>
))}
<button onClick={handleClickRefetch}>Fetch Heroes</button>
</div>
);
enabled๋ ์ฟผ๋ฆฌ๊ฐ ์๋์ผ๋ก ์คํ๋์ง ์๋๋ก ํ ๋ ์ค์ ํ ์ ์๋ค.false๋ฅผ ์ฃผ๋ฉด ์๋ ์คํ๋์ง ์๋๋ค. ๋ํ useQuery ๋ฆฌํด ๋ฐ์ดํฐ์ค status๊ฐ idle ์ํ๋ก ์์ํ๋ค.refetch๋ ์ฟผ๋ฆฌ๋ฅผ์๋์ผ๋ก ๋ค์ ์์ฒญํ๋ ๊ธฐ๋ฅ์ด๋ค. ์ฟผ๋ฆฌ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ์ค๋ฅ๋ง ๊ธฐ๋ก๋๋ค. ์ค๋ฅ๋ฅผ ๋ฐ์์ํค๋ ค๋ฉดthrowOnError์์ฑ์true๋กํด์ ์ ๋ฌํด์ผ ํ๋ค.- ๋ณดํต ์๋์ผ๋ก ์ฟผ๋ฆฌ ์์ฒญ์ ํ์ง ์๊ณ ๋ฒํผ ํด๋ฆญ์ด๋ ํน์ ์ด๋ฒคํธ๋ฅผ ํตํด ์์ฒญ์ ์๋ํ ๋ ๊ฐ์ด ์ฌ์ฉํ๋ค.
- ๋ง์ฝ
enabled: false๋ฅผ ์คฌ๋ค๋ฉดqueryClient๊ฐ ์ฟผ๋ฆฌ๋ฅผ ๋ค์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ๋ค ์คinvalidateQueries์refetchQueries๋ฅผ ๋ฌด์ํ๋ค.
retry
const result = useQuery(["todos", 1], fetchTodoListPage, {
retry: 10, // ์ค๋ฅ๋ฅผ ํ์ํ๊ธฐ ์ ์ ์คํจํ ์์ฒญ์ 10๋ฒ ์ฌ์๋ํฉ๋๋ค.
});
- retry (boolean | number | (failureCount: number, error: TError) => boolean)
- retry๋ ์ฟผ๋ฆฌ๊ฐ
์คํจํ๋ฉด useQuery๋ฅผํน์ ํ์(๊ธฐ๋ณธ๊ฐ 3)๋งํผ ์ฌ์์ฒญํ๋ ์ต์ ์ด๋ค. - retry๊ฐ
false์ธ ๊ฒฝ์ฐ ์คํจํ ์ฟผ๋ฆฌ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค์ ์๋ํ์ง ์๋๋ค. true์ธ ๊ฒฝ์ฐ์๋ ์คํจํ ์ฟผ๋ฆฌ์ ๋ํด์ ๋ฌดํ ์ฌ์์ฒญ์ ์๋ํ๋ค.- ๊ฐ์ผ๋ก
์ซ์๋ฅผ ๋ฃ์ ๊ฒฝ์ฐ ์คํจํ ์ฟผ๋ฆฌ๊ฐ ํด๋น ์ซ์๋ฅผ ์ถฉ์กฑํ ๋๊น์ง ์์ฒญ์ ์ฌ์๋ํ๋ค.
onSuccess onError onSettled
const onSuccess = useCallback((data) => {
console.log("Success", data);
}, []);
const onError = useCallback((err) => {
console.log("Error", err);
}, []);
const onSettled = useCallback(() => {
console.log("Settled");
}, []);
const { isLoading, isFetching, data, isError, error, refetch } = useQuery(
"super-heroes",
getSuperHero,
{
onSuccess,
onError,
onSettled,
}
);
onSuccessํจ์๋ ์ฟผ๋ฆฌ ์์ฒญ์ด ์ฑ๊ณต์ ์ผ๋ก ์งํ๋์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋ ์บ์๊ฐ ์ ๋ฐ์ดํธ๋ ๋๋ง๋ค ์คํ๋๋ค.onErrorํจ์๋ ์ฟผ๋ฆฌ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ณ ์ค๋ฅ๊ฐ ์ ๋ฌ๋๋ฉด ์คํ๋๋ค.onSettledํจ์๋ ์ฟผ๋ฆฌ ์์ฒญ์ด ์ฑ๊ณต, ์คํจ ๋ชจ๋ ์คํ๋๋ค.
select
const { isLoading, isFetching, data, isError, error, refetch } = useQuery(
"super-heroes",
getSuperHero,
{
onSuccess,
onError,
select(data) {
const superHeroNames = data.data.map((hero: Data) => hero.name);
return superHeroNames;
},
}
);
return (
<div>
<button onClick={handleClickRefetch}>Fetch Heroes</button>
{data.map((heroName: string, idx: number) => (
<div key={idx}>{heroName}</div>
))}
</div>
);
select์ต์ ์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ ํจ์์์ ๋ฐํ๋ ๋ฐ์ดํฐ์ ์ผ๋ถ๋ฅผ ๋ณํํ๊ฑฐ๋ ์ ํํ ์ ์๋ค.
keepPreviousData
const fetchColors = (pageNum: number) => {
return axios.get(`http://localhost:4000/colors?_limit=2&_page=${pageNum}`);
};
const { isLoading, isError, error, data, isFetching, isPreviousData } =
useQuery(["colors", pageNum], () => fetchColors(pageNum), {
keepPreviousData: true,
});
- keepPreviousData๋ฅผ
true๋ก ์ค์ ํ๋ฉด ์ฟผ๋ฆฌ ํค๊ฐ ๋ณ๊ฒฝ๋์ด์ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ ๋์์๋๋ง์ง๋ง data ๊ฐ์ ์ ์งํ๋ค. - keepPreviousData์
ํ์ด์ง๋ค์ด์ ๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ ๊ตฌํํ ๋ ํธ๋ฆฌํ๋ค. ์บ์๋์ง ์์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ฌ ๋ ๋ชฉ๋ก์ด๊น๋นก๊น๋นก๊ฑฐ๋ฆฌ๋ ํ์์ ๋ฐฉ์งํ ์ ์๋ค. - ๋ํ,
isPreviousData๊ฐ์ผ๋ก ํ์ฌ์ ์ฟผ๋ฆฌ ํค์ ํด๋นํ๋ ๊ฐ์ธ์ง ํ์ธํ ์ ์๋ค.ํ์ด์ง๋ค์ด์ ์ผ๋ก ์๋ฅผ๋ค๋ฉด, ์์ง ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์บ์ฑ๋์ง ์์๋ค๋ฉด, ์ด์ ๋ฐ์ดํฐ์ด๋ฏ๋ก true๋ฅผ ๋ฐํํ๊ณ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์ ์์ ์ผ๋ก ๋ฐ์์ ธ์๋ค๋ฉด ์ด์ ๋ฐ์ดํฐ๊ฐ ์๋๋ฏ๋ก false๋ฅผ ๋ฐํํ๋ค.
placeholderData
function Todos() {
const placeholderData = useMemo(() => generateFakeTodos(), []);
const result = useQuery("todos", () => fetch("/todos"), { placeholderData });
}
- placeholderData๋ฅผ ์ฌ์ฉํ๋ฉด
mock ๋ฐ์ดํฐ์ค์ ๋ ๊ฐ๋ฅํ๋ค. ๋์ ์บ์ฑ์ด ์๋๋ค๋ ๋จ์ ์ด์๋ค.
Parallel
const { data: superHeroes } = useQuery("super-heroes", fetchSuperHeroes);
const { data: friends } = useQuery("friends", fetchFriends);
- ๋ช ๊ฐ์ง ์ํฉ์ ์ ์ธํ๋ฉด ์ฟผ๋ฆฌ ์ฌ๋ฌ๊ฐ๊ฐ ์ ์ธ๋์ด ์๋ ์ผ๋ฐ์ ์ธ ์ํฉ์ผ ๋ ์ฟผ๋ฆฌ ํจ์๋ค์
๊ทธ๋ฅ ๋ณ๋ ฌ๋ก ์์ฒญ๋์ ์ฒ๋ฆฌ๋๋ค. - ์ด๋ฌํ ํน์ง์ ์ฟผ๋ฆฌ ์ฒ๋ฆฌ์
๋์์ฑ์ ๊ทน๋ํ ์ํจ๋ค.
const queryResults = useQueries(
heroIds.map((id) => ({
queryKey: ["super-hero", id],
queryFn: () => fetchSuperHero(id),
}))
);
- ํ์ง๋ง, ์ฟผ๋ฆฌ ์ฌ๋ฌ๊ฐ๋ฅผ ๋์์ ์ํํด์ผ ํ๋๋ฐ, ๋ ๋๋ง์ด ๊ฑฐ๋ญ๋๋ ์ฌ์ด์ฌ์ด์ ๊ณ์ ์ฟผ๋ฆฌ๊ฐ ์ํ๋์ด์ผ ํ๋ค๋ฉด ์ฟผ๋ฆฌ๋ฅผ ์ํํ๋ ๋ก์ง์ด hook๋ฃฐ์ ์๋ฐฐ๋ ์๋ ์๋ค. ์ด๋ด ๋๋
useQueries๋ฅผ ์ฌ์ฉํ๋ค.
Dependent Queries
์ข ์ ์ฟผ๋ฆฌ๋ ์ด๋ค A๋ผ๋ ์ฟผ๋ฆฌ๊ฐ ์๋๋ฐ ์ด A์ฟผ๋ฆฌ๋ฅผ ์คํํ๊ธฐ ์ ์ ์ฌ์ ์ ์๋ฃ๋์ผ ํ๋ B ์ฟผ๋ฆฌ๊ฐ ์๋๋ฐ, ์ด๋ฌํ B์ฟผ๋ฆฌ์ ์์กดํ๋ A์ฟผ๋ฆฌ๋ฅผ ์ข ์ ์ฟผ๋ฆฌ๋ผ๊ณ ํ๋ค.- react-query์์๋ ์ฟผ๋ฆฌ๋ฅผ ์คํํ ์ค๋น๊ฐ ๋์๋ค๋ ๊ฒ์ ์๋ ค์ฃผ๋
enabled์ต์ ์ ํตํด ์ข ์ ์ฟผ๋ฆฌ๋ฅผ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค.
const DependantQueriesPage = ({ email }: Props) => {
// ์ฌ์ ์ ์๋ฃ๋์ผํ ์ฟผ๋ฆฌ
const { data: user } = useQuery(['user', email], () =>
fetchUserByEmail(email)
);
const channelId = user?.data.channelId;
// user ์ฟผ๋ฆฌ์ ์ข
์ ์ฟผ๋ฆฌ
const { data } = useQuery(
['courses', channelId],
() => fetchCoursesByChannelId(channelId),
{ enabled: !!channelId }
);
useQueryClient
- useQueryClient๋
QueryClient์ธ์คํด์ค๋ฅผ ๋ฐํํ๋ค. QueryClient๋ ์บ์์ ์ํธ์์ฉ ํ๋ค.
import { useQueryClient } from "react-query";
const queryClient = useQueryClient();
Initial Query Data
- ์ฟผ๋ฆฌ์ ๋ํ
์ด๊ธฐ ๋ฐ์ดํฐ๊ฐ ํ์ํ๊ธฐ ์ ์ ์บ์์ ์ ๊ณตํ๋ ๋ฐฉ๋ฒ์ด ์๋ค. ์๋ ์์ ์ฐธ๊ณ - initialData ์ต์ ์ ํตํด์ ์ฟผ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ์ฑ์ฐ๋๋ฐ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ด๊ธฐ ๋ก๋ ์ํ๋ ๊ฑด๋๋ ์ ์๋ค.
const useSuperHeroData = (heroId: string) => {
const queryClient = useQueryClient();
return useQuery(['super-hero', heroId], fetchSuperHero, {
initialData: () => {
const queryData = queryClient.getQueryData('super-heroes') as any;
const hero = queryData?.data?.find(
(hero: Hero) => hero.id === parseInt(heroId)
);
if (hero) return { data: hero };
return undefined;
},
});
};
- ์ฐธ๊ณ ๋ก ์ ์์ ์์
queryClient.getQueryData๋ฉ์๋๋ ๊ธฐ์กด ์ฟผ๋ฆฌ์์บ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๋ฐ ์ฌ์ฉํ ์ ์๋ ๋๊ธฐ ํจ์์ด๋ค. ์ฟผ๋ฆฌ๊ฐ ์กด์ฌํ์ง ์์ผ๋ฉดundefined๋ฅผ ๋ฐํํ๋ค.
Infinite Queries
- ๋ฌดํ ์ฟผ๋ฆฌ๋ ๋ฌดํ ์คํฌ๋กค์ด๋ load more ๊ฐ์ด ํน์ ์กฐ๊ฑด์์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ๋ฐ์์ค๋ ๊ธฐ๋ฅ ๊ตฌํํ ๋ ์ฌ์ฉํ๋ฉด ์ ์ฉํ๋ค.
import { useInfiniteQuery } from "react-query";
const fetchColors = ({ pageParam = 1 }) => {
return axios.get(`http://localhost:4000/colors?_limit=2&_page=${pageParam}`);
};
const InfiniteQueries = () => {
const { data, hasNextPage, isFetching, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery("colors", fetchColors, {
getNextPageParam: (lastPage, allPages) => {
if (allPages.length < 4) {
return allPages.length + 1;
} else {
return undefined;
}
},
});
return (
<div>
{data?.pages.map((group, idx) => (
<Fragment key={idx}>
{group.data.map((color: any) => (
<h2 key={color.id}>
{color.id}. {color.label}
</h2>
))}
</Fragment>
))}
<div>
<button disabled={!hasNextPage} onClick={() => fetchNextPage()}>
LoadMore
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? "Fetching..." : null}</div>
</div>
);
};
Returns
useInfiniteQuery๋ ๊ธฐ๋ณธ์ ์ผ๋ก useQuery์ ์ฌ์ฉ๋ฒ์ ๋น์ทํ์ง๋ง ์ฐจ์ด์ ์ด ์๋ค.- useInfiniteQuery๋ ๋ฐํ๊ฐ์ผ๋ก isFetchingNextPage, isFetchingPreviousPage, fetchNextPage, fetchPreviousPage ๋ฑ์ด ์ถ๊ฐ์ ์ผ๋ก ์๋ค.
fetchNextPage๋ฅผ ํธ์ถํ๋ฉด ๋ค์ ํ์ด์ง๋ฅผ fetch ํ ์ ์๋ค.fetchPreviousPage๋ฅผ ํธ์ถํ๋ฉด ์ด์ ํ์ด์ง๋ฅผ fetch ํ ์ ์๋ค.isFetchingNextPage์fetchNextPage๋ฉ์๋๊ฐ ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ true์ด๋ค. ์ฆ, ์ด๊ธฐ๊ฐ์ true์ด๊ณ , ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ฉด false๊ฐ ๋๋ค.isFetchingPreviousPage์fetchPreviousPage๋ฉ์๋๊ฐ ์ด์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ true์ด๋ค. ์ฆ, ์ด๊ธฐ๊ฐ์ true์ด๊ณ , ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ฉด false๊ฐ ๋๋ค.hasNextPage๋ ๊ฐ์ ธ์ฌ ์ ์๋ ๋ค์ ํ์ด์ง๊ฐ ์์ ๊ฒฝ์ฐ true์ด๋ค.
์ต์
pageParam์ด๋ผ๋ ํ๋กํผํฐ๊ฐ ์กด์ฌํ๋ฉฐ queryFn์ ํ ๋นํด์ค์ผํ๋ค. ์ด๋ ๊ธฐ๋ณธ๊ฐ์1๋ก ํด์ค์ผํ๋ค.- ๊ทธ๋ฆฌ๊ณ
getNextPageParam์ ์ด์ฉํด์ ํ์ด์ง๋ฅผ ์ฆ๊ฐ์ํจ๋ค.- ์ด๋, getNextPageParam์ ์ฒซ ๋ฒ์งธ ์ธ์
lastPage๋ fetch ํด์จ ๊ฐ์ฅ ์ต๊ทผ์ ๊ฐ์ ธ์จ ํ์ด์ง ๋ชฉ๋ก์ด๋ค. - ๋ ๋ฒ์งธ ์ธ์
allPages๋ ํ์ฌ๊น์ง ๊ฐ์ ธ์จ ๋ชจ๋ ํ์ด์ง๋ค ๋ฐ์ดํฐ์ด๋ค.
- ์ด๋, getNextPageParam์ ์ฒซ ๋ฒ์งธ ์ธ์
getPreviousPageParam๋ ์กด์ฌํ๋ฉฐ,getNextPageParam์ ๋ฐ๋์ ์์ฑ์ ๊ฐ๊ณ ์๋ค.- ๊ทธ๋ฆฌ๊ณ ์์ฒญ์ด ์ฑ๊ณตํ๊ณ ๋ฐํ๋๋ data๋
pages๋ผ๋ ํ๋กํผํฐ๋ฅผ ๊ฐ๊ณ ์์ผ๋ฉฐ, pages๋group์ด๋ผ๋ ํ๋กํผํฐ๋ฅผ ๊ฐ๊ณ ์๋ค.
useMutation mutate
- useMutation ๊ณต์ ์ฌ์ดํธ
- react-query์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ Get ํ ๋๋ useQuery๋ฅผ ์ฌ์ฉํ๋ค.
- ๋ง์ฝ ์๋ฒ์ data๋ฅผ post, patch, put, delete์ ๊ฐ์ด ์์ ํ๊ณ ์ ํ๋ค๋ฉด ์ด๋๋ useMutation์ ์ด์ฉํ๋ค.
- ์์ฝํ์๋ฉด
R(read)๋ useQuery,CUD(Create, Update, Delete)๋ useMutation์ ์ฌ์ฉํ๋ค.
const CreateTodo = () => {
const mutation = useMutation(createTodo, {
onMutate() {},
onSuccess(data) {
console.log(data);
},
onError(err) {
console.log(err);
},
});
const onCreateTodo = (e) => {
e.preventDefault();
mutation.mutate({ title });
};
return <>...</>;
};
- useMutation์ ๋ฐํ ๊ฐ์ธ mutation ๊ฐ์ฒด์
mutate๋ฉ์๋๋ฅผ ์ด์ฉํด์ ์์ฒญ ํจ์๋ฅผ ํธ์ถํ ์ ์๋ค. - mutate๋
onSuccess,onError๋ฉ์๋๋ฅผ ํตํด ์ฑ๊ณต ํ์ ์, ์คํจ ํ์ ์ response ๋ฐ์ดํฐ๋ฅผ ํธ๋ค๋งํ ์ ์๋ค. onMutate๋ mutation ํจ์๊ฐ ์คํ๋๊ธฐ ์ ์ ์คํ๋๊ณ mutation ํจ์๊ฐ ๋ฐ์ ๋์ผํ ๋ณ์๊ฐ ์ ๋ฌ๋๋ค.
const mutation = useMutation(addTodo);
try {
const todo = await mutation.mutateAsync(todo);
console.log(todo);
} catch (error) {
console.error(error);
} finally {
console.log("done");
}
- ๋ง์ฝ, useMutation์ ์ฌ์ฉํ ๋ promise ํํ์ response๊ฐ ํ์ํ ๊ฒฝ์ฐ๋ผ๋ฉด
mutateAsync๋ฅผ ์ฌ์ฉํด์ ์ป์ด์ฌ ์ ์๋ค. - Redux์ ๊ฐ์ Request ์ฑ๊ณต ์ก์
์ ๋ฏธ๋ค์จ์ด์์ ํ์ธํ์ฌ ์ถ๊ฐ ์ก์
์ ์คํํ๋ ๊ฒ ๊ฐ์ ์์
์ ํ ๋๋, mutate๋ onSuccess, onError์ ๊ฐ์ ๋ฉ์๋๋ฅผ ๊ฐ์ด ์ฌ์ฉํด์ผ ๋๊ธฐ๋๋ฌธ์
mutateAsync๊ฐ ๋ ๊ฐ๋ ์ฑ์ด ์ข๋ค
์ฟผ๋ฆฌ ๋ฌดํจํ
- ์ด๊ฒ์ ๊ฐ๋ ์ ์ผ๋ก ํ๋ฉด์ ์ต์ ์ํ๋ก ์ ์งํ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด๋ค..
- ์๋ฅผ ๋ค๋ฉด, ๊ฒ์ํ ๋ชฉ๋ก์์ ์ด๋ค ๊ฒ์๊ธ์
์์ฑ(Post)ํ๊ฑฐ๋ ๊ฒ์๊ธ์์ ๊ฑฐ(Delete)ํ์ ๋ ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ํ ๋ชฉ๋ก์ ์ค์๊ฐ ์ต์ ํ ํด์ผ๋ ๋๊ฐ ์๋ค. - ํ์ง๋ง ์ด๋,
query Key๊ฐ ๋ณํ์ง ์์ผ๋ฏ๋ก ์ด๋ด๋ ๊ฐ์ ๋ฆฌํ๋ ์ฌ๋ฅผ ์งํํด์ผ ํ๋๋ฐ ์ด๋,queryClient์invalidateQueries()๋ฉ์๋๋ฅผ ์ด์ฉํ๋ค. - ์ฆ, query๊ฐ ์ค๋ ๋์๋ค๋ ๊ฒ์ ํ๋จํ๊ณ ๋ค์
refetch๋ฅผ ํ ๋ ์ฌ์ฉํ๋ค!
import { useMutation, useQuery, useQueryClient } from "react-query";
const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
onSuccess(data) {
queryClient.invalidateQueries("super-heroes"); // ์ด key์ ํด๋น ํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฌดํจํ!
console.log(data);
},
onError(err) {
console.log(err);
},
});
};
- ๋ง์ฝ ๋ฌดํจํ ํ๋ ค๋ ํค๊ฐ ์ฌ๋ฌ๊ฐ๋ผ๋ฉด ์๋ ์์ ์ ๊ฐ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ฐฐ์ด๋ก ๋ณด๋ด์ฃผ๋ฉด ๋๋ค.
queryClient.invalidateQueries(["super-heroes", "posts", "comment"]);
- ์์
enabled/refetch์์๋ ์ธ๊ธํ์ง๋งenabled: false์ต์ ์ ์ฃผ๋ฉดqueryClient๊ฐ ์ฟผ๋ฆฌ๋ฅผ ๋ค์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ๋ค ์คinvalidateQueries์refetchQueries๋ฅผ ๋ฌด์ํ๋ค.
์บ์ ๋ฐ์ดํฐ ์ฆ์ ์ ๋ฐ์ดํธ
- ๋ฐ๋ก ์์์
queryClient.invalidateQueries๋ฅผ ์ด์ฉํด ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ์์๋ดค๋๋ฐ queryClient.setQueryData๋ฅผ ์ด์ฉํด์๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์ ๋ฐ์ดํธ ํ ์ ์๋ค. queryClient.setQueryData๋ ์ฟผ๋ฆฌ์ ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋๋๊ธฐ ํจ์์ด๋ค.
const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
onSuccess(data) {
queryClient.setQueryData("super-heroes", (oldData: any) => {
return {
...oldData,
data: [...oldData.data, data.data],
};
});
},
onError(err) {
console.log(err);
},
});
};
Optimistic Update
Optimistic Update(๋๊ด์ ์ ๋ฐ์ดํธ)๋ ์๋ฒ ์ ๋ฐ์ดํธ ์ UI์์๋ ์ด์ฐจํผ ์ ๋ฐ์ดํธ ํ ๊ฒ์ด๋ผ๊ณ (๋๊ด์ ์ธ) ๊ฐ์ ํด์๋ฏธ๋ฆฌ UI๋ฅผ ์ ๋ฐ์ดํธ์์ผ์ฃผ๊ณ ์๋ฒ๋ฅผ ํตํด ๊ฒ์ฆ์ ๋ฐ๊ณ ์ ๋ฐ์ดํธ ๋๋ ๋กค๋ฐฑํ๋ ๋ฐฉ์์ด๋ค.- ์๋ฅผ ๋ค์ด facebook์ ์ข์์ ๋ฒํผ์ด ์๋๋ฐ ์ด๊ฒ์ ์ ์ ๊ฐ ๋๋ฅธ๋ค๋ฉด ์ผ๋จ client ์ชฝ state๋ฅผ ๋จผ์ ์ ๋ฐ์ดํธํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ง์ฝ์ ์คํจ ํ๋ค๋ฉด ์์ state๋ก ๋์๊ฐ๊ณ ์ฑ๊ณตํ๋ฉด ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ค์ fetchํด์ ์๋ฒ ๋ฐ์ดํฐ์ ํ์คํ ์ฐ๋์ ์งํํ๋ค.
- Optimistic Update๊ฐ ์ ๋ง ์ ์ฉํ ๋๋ ์ธํฐ๋ท ์๋๊ฐ ๋๋ฆฌ๊ฑฐ๋ ์๋ฒ๊ฐ ๋๋ฆด ๋ ์ ์ ๊ฐ ํ ์ก์ ์ ๊ธฐ๋ค๋ฆด ํ์ ์์ด ๋ฐ๋ก ์ ๋ฐ์ดํธ ๋๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ธฐ ๋๋ฌธ์ UX์ ์ผ๋ก ์ข๋ค.
const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
async onMutate(newHero) {
// ๋๊ด์ ์
๋ฐ์ดํธ๋ฅผ ๋ฎ์ด์ฐ์ง ์๊ธฐ ์ํด ์ฟผ๋ฆฌ๋ฅผ ์๋์ผ๋ก ์ญ์ ํ๋ค.
await queryClient.cancelQueries("super-heroes");
// ์ด์ ๊ฐ
const previousHeroData = queryClient.getQueryData("super-heroes");
// ์๋ก์ด ๊ฐ์ผ๋ก ๋๊ด์ ์
๋ฐ์ดํธ ์งํ
queryClient.setQueryData("super-heroes", (oldData: any) => {
return {
...oldData,
data: [
...oldData.data,
{ ...newHero, id: oldData?.data?.length + 1 },
],
};
});
// ๊ฐ์ด ๋ค์ด์๋ context ๊ฐ์ฒด๋ฅผ ๋ฐํ
return {
previousHeroData,
};
},
// mutation์ด ์คํจํ๋ฉด onMutate์์ ๋ฐํ๋ context๋ฅผ ์ฌ์ฉํ์ฌ ๋กค๋ฐฑ ์งํ
onError(error, hero, context: any) {
queryClient.setQueryData("super-heroes", context.previousHeroData);
},
// ์ค๋ฅ ๋๋ ์ฑ๊ณต ํ์๋ ํญ์ ๋ฆฌํ๋ ์ฌ
onSettled() {
queryClient.invalidateQueries("super-heroes");
},
});
};
- ์ฐธ๊ณ ๋ก ์ ์์ ์์
cancelQueries๋ ์ฟผ๋ฆฌ๋ฅผ์๋์ผ๋ก ์ทจ์์ํฌ ์ ์๋ค. ์ทจ์์ํฌ query์ queryKey๋ฅผ cancelQueries์ ์ธ์๋ก ๋ณด๋ด ์คํ์ํจ๋ค. - ์๋ฅผ ๋ค์ด, ์์ฒญ์ ์๋ฃํ๋ ๋ฐ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ๊ฒฝ์ฐ, ์ฌ์ฉ์๊ฐ ์ทจ์ ๋ฒํผ์ ํด๋ฆญํ์ฌ ์์ฒญ์ ์ค์งํ๋ ๊ฒฝ์ฐ ์ด์ฉํ ์ ์๋ค.
useQueryErrorResetBoundary
- useQueryErrorResetBoundary ๊ณต์ ๋ฌธ์
- react-query์์๋
Error๊ฐ ๋ฐ์ํ์ ๋ ๋์ํ ์ ์๋useQueryErrorResetBoundary hook์ ์ ๊ณตํ๋ค. - ์์ธํ ๋ด์ฉ์ ์ค๋ช ํ๊ธฐ๋ณด๋ค๋ ๊ฐ๋จํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ ์์๋ณด๊ณ , ์์ฉํด์ ์ฌ์ฉํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
- ์ฐ์ , useQueryErrorResetBoundary๋
ErrorBoundary์ ํจ๊ป ์ฌ์ฉ๋๋๋ฐ ์ด๋,react-error-boundary๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น๊ฐ ํ์ํ๋ค.
npm i react-error-boundary
๋๋
yarn add react-error-boundary
- ์ค์น ํ์ ์๋์ ๊ฐ์ QueryErrorBoundary๋ผ๋ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ๊ณ , ๊ทธ ๋ด๋ถ์
useQueryErrorResetBoundaryํ ์ ํธ์ถํดresetํจ์๋ฅผ ๊ฐ์ ธ์จ๋ค. - ์๋ ์ฝ๋ ๋ด์ฉ์ ๋จ์ํ๋ค.
- Error๊ฐ ๋ฐ์ํ๋ฉด ErrorBoundary์
fallbackRenderprop์ผ๋ก ๋๊ธด ๋ด์ฉ์ด ๋ ๋๋ง๋๊ณ , ๊ทธ๋ ์ง ์์ผ๋ฉด children ๋ด์ฉ์ด ๋ ๋๋ง๋๋ค. - ๋ํ, fallbackRender์ ๋ฃ์ด์ฃผ๋ ์ฝ๋ฐฑ ํจ์ ๋งค๊ฐ ๋ณ์๋ก
resetErrorBoundary๋ฅผ ๊ตฌ์กฐ ๋ถํด ํ ๋น์ ํตํด ๊ฐ์ ธ์ฌ ์ ์๋๋ฐ, ์ด๋ฅผ ํตํด ๋ชจ๋ ์ฟผ๋ฆฌ ์๋ฌ๋ฅผ์ด๊ธฐํํ ์ ์๋ค. ์๋ ์ฝ๋ ๊ฐ์ ๊ฒฝ์ฐ์๋ button์ ํด๋ฆญํ๋ฉด ์๋ฌ๋ฅผ ์ด๊ธฐํํ๊ฒ๋ ์์ฑํ๋ค.
- Error๊ฐ ๋ฐ์ํ๋ฉด ErrorBoundary์
import { useQueryErrorResetBoundary } from "react-query"; // (*)
import { ErrorBoundary } from "react-error-boundary"; // (*)
interface Props {
children: React.ReactNode;
}
const QueryErrorBoundary = ({ children }: Props) => {
const { reset } = useQueryErrorResetBoundary(); // (*)
return (
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<div>
Error!!
<button onClick={() => resetErrorBoundary()}>Try again</button>
</div>
)}
>
{children}
</ErrorBoundary>
);
};
export default QueryErrorBoundary;
- ๊ทธ๋ฆฌ๊ณ App.js์๋ค QueryErrorBoundary ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๋ฉด๋๋ค. ์ฌ๊ธฐ์ ์ฃผ์ ํ ์ ์ queryClient ์ต์
์๋ค
{ useErrorBoundary: true }๋ฅผ ์ถ๊ฐํด์ผ๋๋ค๋ ์ ์ด๋ค. ๊ทธ๋์ผ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ ๋ErrorBoundary์ปดํฌ๋ํธ๊ฐ ๊ฐ์งํ ์ ์๋ค.
import { QueryClientProvider, QueryClient } from "react-query";
import QueryErrorBoundary from "./components/ErrorBoundary"; // (*)
const queryClient = new QueryClient({
defaultOptions: {
queries: {
useErrorBoundary: true, // (*)
},
},
});
export const AppContext = createContext < any > undefined;
function App() {
return (
<AppContext.Provider value={user}>
<QueryClientProvider client={queryClient}>
<QueryErrorBoundary>{/* code */}</QueryErrorBoundary>
</QueryClientProvider>
</AppContext.Provider>
);
}
export default App;
- ์ถ๊ฐ์ ์ผ๋ก useQueryErrorResetBoundary hook์ ์ฌ์ฉํ์ง์๊ณ
QueryErrorResetBoundary์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํด์ Error๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ๋ ์๋๋ฐ, ๊ฒฐ๊ตญ ์ปจ์ ์ ๋น์ทํด์ ์๋ ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๋ฉด ์ถฉ๋ถํ ์ดํดํ ์ ์์ ๊ฒ์ด๋ค. - QueryErrorResetBoundary