
💡 본인은 리덕스를 상당히 애용하는 편으로 캐시된 데이터를 조작하려고 할때 자연스럽게 RTK Query를 도입하고 쏠쏠하게 써먹었던 경험이 있었습니다.
하지만 점점 전역 상태 로직에 대해서 이해를 어느정도 하고 나니 리덕스는 정석 그 자체이지만 이것보다 조금 더 가벼운 Zustand, Recoil에 대해 학습을 어느정도 하고 나니까 굳이 리덕스를 작은 어플리케이션 구성 시 사용할 이유가 있을까? 라는 생각이 들더군요…
따라서 이번 기회에 많은 사람들이 애용하는 Tanstack-Query에 대해서도 학습을 하고 기존 코드를 그에 맞게 리팩토링 과정도 거칠려고 합니다.
우선 늘 참고사항의 우선이 되야 하는 공식 문서부터 보고 가시길 바랍니다 !
Overview | TanStack Query Docs
npm i @tanstack/react-query
or
yarn add @tanstack/react-query
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Home />
</QueryClientProvider>
);
}
const queryClient = new QueryClient();client={queryClient}import { useQuery } from "react-query";
// 주로 사용되는 3가지 return값 외에도 더 많은 return 값들이 있다.
const { data, isLoading, error } = useQuery(queryKey, queryFn, options);// 문자열
useQuery('todos', ...)
// 배열
useQuery(['todos', '1'], ...)const { data, isLoading, error } = useQuery([’todos’, id], () ⇒ axios.get(`http://…/${id}`));- useQuery('todos', fetchTodos);
- useQuery(['todos', todoId], () => fetchTodoById(todoId));
- useQuery(['todos', todoId], async () => {
const data = await fetchTodoById(todoId);
return data
});
- useQuery(['todos', todoId], ({ queryKey }) => fetchTodoById(queryKey[1]));쿼리가 자동으로 실행되지 않게 설정하는 옵션이다.
// id가 존재할 때만 쿼리 요청을 한다.
const { data } = useQuery(
['todos', id],
() => fetchTodoById(id),
{
enabled: !!id,
}
);
❗️useQuery 예시
const { data: tasks, isLoading } = useQuery("getTasks", getTodo, {
refetchOnWindowFocus: false,
staleTime: 60 * 1000, // 1분
});
const { data: tasks, isLoading } = useQuery("getTasks", getTodo, {
refetchOnWindowFocus: false,
staleTime: 60 * 1000, // 1분
onError: (error) => {
if (error.response?.data.code === 401) {
// ...
}
}
});
const data = useMutation(API 호출 함수, 콜백);
Add 후 수동적으로 Fetch를 해줘야 화면에 보여진다는 불편함이 있다.
이 문제점을 해결하기 위해서는 쿼리 무효화(Invalidation)를 시켜줘야 한다.
이 전에 캐싱된 쿼리를 직접 무효화 시킨 후 데이터를 새로 패칭해줄 수 있다.
import { useMutation, useQueryClient } from 'react-query';
const AddSuperHero = () => {
✅ const queryClient = useQueryClient();
const addSuperHeror = (hero) => {
return axios.post('http://localhost:4000/superheroes', hero);
};
const { mutate: addHero, isLoading, isError, error } = useMutation(addSuperHero, {
onSuccess: () => {
// 캐시가 있는 모든 쿼리 무효화
✅ queryClient.invalidateQueries();
// queryKey가 'super-heroes'로 시작하는 모든 쿼리 무효화
✅ queryClient.invalidateQueries('super-heroes');
}
});
const handleAddHeroClick = () => {
const hero = { 이름, 성별 };
addHero(hero);
};
if (isLoading) {
return <h2>Loading...</h2>;
}
if (isError) {
return <h2>{error.message}</h2>;
}
}
useQuery를 사용할 때 다음과 같은 에러가 날 수 있다.
react-dom.development.js:16227
Uncaught Error: Invalid hook call.
Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
위 사항에 하나라도 해당하면 이런 에러가 난다고 한다.
보통 fetch, axios, … 같은 패칭 훅은 useEffect, useMemo, useReducer 등등 에서 쓰이곤 했는데 React-Query는 React-Hook 내부에서 쓰면 안되고 독립적으로 쓰여야 한다! (이것마저도 RTK-Query와 똑같아서 사용하는데 오히려 반가울 것 같다 ㅎㅎ)