React-Query는 비동기 로직을 리액트스럽게 다룰 수 있게 해주는 라이브러리이다. 이를 사용하면 server state를 아주 효율적으로 관리할 수 있다.
npm i react-query
yarn add react-query
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Home />
</QueryClientProvider>
);
}
QueryClientProvider
로 앱을 감싸준다. const queryClient = new QueryClient();
client={queryClient}
GET
요청과 같은 Read 작업을 할 때 사용되는 Hook이다. import { useQuery } from "react-query";
// 주로 사용되는 3가지 return값 외에도 더 많은 return 값들이 있다.
const { data, isLoading, error } = useQuery(queryKey, queryFn, options)
QueryKey
를 기반으로 데이터 캐싱을 관리한다. // 문자열
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]));
React-Query docs를 참고하면 더 많은 옵션들을 볼 수 있다.
// id가 존재할 때만 쿼리 요청을 한다.
const { data } = useQuery(
['todos', id],
() => fetchTodoById(id),
{
enabled: !!id,
}
);
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 addSuperHero = (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
에러를 읽어보면
1. React와 React DOM의 버전이 맞지 않거나
2. Hooks 규칙을 지키지 않고 있거나
3. 같은 앱에 리액트가 여러개 있거나
일 때 이런 에러가 난다고 한다.
나는 리액트 공식 홈페이지를 통해 에러를 해결했다.
여태까지 React-Query를 사용하기 전까지는 useEffect 안에서 필요한 api를 실행하도록 코드를 작성했어서 useQuery할 때도 useEffect 안에서 불러왔는데 이게 문제였다.
아래로 더 내려가서 읽어보면 다음과 같은 글이 있는데 마지막 문구를 보면 useEffect 안에서 Hooks를 호출하지 말라고 한다.
useEffect, useMemo useReducer 안에서 React-Query를 사용하면 안된다!
Apollo-Client를 사용할 때도 useEffect를 쓰지는 않고 useQuery를 바로 불러왔기 때문에 Apollo-Client와 유사하게 사용하면 될 것 같다.
와.......... 초등학생도 이해할수있을정도로 정리를 너무잘해주셔서 굉장히 많은 도움받았습니다 ㅠㅠ