react-query는 서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러핸들링 등 비동기 과정을 더욱 편하게 하는데 사용된다.
첫번째 파라미터로 unique Key가 들어가고, 두번째 파라미터로 비동기 함수(api호출 함수)가 들어간다.
import { useQuery } from "react-query" 한 뒤
let result = useQuery("name", ()=>
axios.get("").then((res)=> { return res.data })
)
예시
const Todos = () => {
const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
refetchOnWindowFocus: false, // 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행할지 여부
retry: 0, // 실패시 재호출 몇번 할지
onSuccess: data => {
console.log(data);
},
onError: e => {
// 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출)
console.log(e.message);
}
});
if (isLoading) {
return <span>Loading...</span>;
}
if (isError) {
return <span>Error: {error.message}</span>;
}
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
};
isLoading, isSuccess 말고 status로 한번에 처리 가능하다.
function Todos() {
const { status, data, error } = useQuery("todos", fetchTodoList);
if (status === "loading") {
return <span>Loading...</span>;
}
if (status === "error") {
return <span>Error: {error.message}</span>;
}
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
enabled 값이 true일때 useQuery를 실행하는데 이것을 이용하면 동기적으로 함수를 실행 할 수 있다.
const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
const { data: nextTodo, error, isFetching } = useQuery(
"nextTodos",
fetchNextTodoList,
{
enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
}
);
useQuery를 비동기로 여러개 실행할 경우 여러 귀찮은 경우가 생긴다.
const usersQuery = useQuery("users", fetchUsers);
const teamsQuery = useQuery("teams", fetchTeams);
const projectsQuery = useQuery("projects", fetchProjects);
// 세 변수를 다 기억해야하고 세 변수에 대한 로딩, 성공, 실패처리를 모두 해야하는 귀찮음이 있다.
이때 promise.all처럼 useQuery를 하나로 묶을 수 있는데, 그것이 useQueries다.
promise.all과 마찬가지로 하나의 배열에 각 쿼리에 대한 상태 값이 객체로 들어온다.
// 아래 예시는 롤 룬과, 스펠을 받아오는 예시다.
const result = useQueries([
{
queryKey: ["getRune", riot.version],
queryFn: () => api.getRunInfo(riot.version)
},
{
queryKey: ["getSpell", riot.version],
queryFn: () => api.getSpellInfo(riot.version)
}
]);
useEffect(() => {
console.log(result); // [{rune 정보, data: [], isSucces: true ...}, {spell 정보, data: [], isSucces: true ...}]
// result.isLoading이 하나라도 로딩중이면 true
const loadingFinishAll = result.some(result => result.isLoading);
// loadingFinishAll이 false이면 최종 완료
console.log(loadingFinishAll);
}, [result]);
Array.prototype.some
배열의 요소가 주어진 판별 함수를 단 한번이라도 만족할 경우 true를 반환한다.
React-Query의 캐싱개념은 stale과 cachetime을 통해 이루어진다.
useQuery 훅을 이용해 호출할 시 전달되는 옵션으로 staletime과 cachetime을 보낼 수 있는데 이 둘을 잘 구별할 줄 알아야 캐싱에 대해 이해할 수 있다.
-cachetime : 캐시 구조에 저장된 데이터는 메모리상에 존재하게 된다. 이 때, 메모리에 저장되어 있는 캐시 데이터가 언제까지 유지될지를 말해주는 옵션이다. default는 5분이다.
자식 내에서 useQueryClient를 호출하면 위에서 생성자 함수, QueryClient를 통해 만들어진 객체의 정보를 얻을 수 있다.
queriesMap이라는 프로퍼티로 브라우저에서 요청했던 ajax 요청결과를 캐싱하여 저장하고 있음을 알 수 있다.
이 순간의 키값은 useQuery를 통해 호출할 당시 첫번째 인자로 전달한 값으로,
이 내부 키값에 대해서 동일한 키를 가진 캐싱값이 존재할 경우, fetching을 진행하지 않고 이 캐싱값을 그대로 다시 사용한다.
단, 해당 useQuery를 호출시 staletime을 따로 지정해주지 않았다면, 항상 캐싱되어 있는 데이터는 stale하다고 여기기 때문에 refetching을 하게 되어 서버에 계속적인 요청을 하게된다.
즉, staletime을 지정해주지 않는다면 react-query의 캐싱 기능을 제대로 활용할 수가 없다.
만약 데이터 구조가 자주 변하는 사이트라면 staletime을 지정하지 않는 편이 좋고,
데이터들이 정적이라면 staletime을 지정해주는 것이 서버의 부담을 줄여줄 것이다.
또 한가지 기억해야할 중요한 사항이 있는데,
옵션에 존재하는 enabled:false 를 설정해놓을 경우, retry를 하는 행위를 사전 차단할 수 있다. 그러나, useQuery의 기능을 사용하지 않겠다고 말해주는 것과 같기 때문에 수동적으로 호출하는 방식이 필요하다.
그것이 바로 useQuery 함수 호출을 하여 리턴되는 객체에 포함되어 있는 refetch 함수다. 여기서 중요한 것은 refetch 함수는 "캐싱 결과와 상관없이 ajax 요청을 날리는 메서드"라는 점이다.
따라서 캐싱을 구현하려고 한다면 enabled 옵션을 false로 두면 안된다.
그러면, true인 상태와 캐싱을 둘 다 구현하려면 어떻게 해야할까.
우선 enabled 옵션에 대해서는 특정한 상태를 충족할 때만 true로 만들고, 그 외에는 false로 하여 초기 요청을 통한 retry로 오류를 생성하는 것을 막는다. (refetch 메서드로 강제호출을 하지 않는다.)
그 뒤에, 조건부로 enabled가 true로 변경되면서 요청을 날리게 되어 성공하면 data 프로퍼티에 그 값이 저장되고, 캐싱에도 저장이 될 것이다. 그렇다면 그 뒤에 data를 가지고 UI를 그려내는 작업을 진행하면 된다.
result.data => 성공시 가져오는 data
result.isLoading => 로딩중일때 true
//로딩중일때 띄워주고 싶은 글
{ result.isLoading ? "로딩중입니다" : result.data}
result.error => 실패했을때 true
//로딩중일때 띄워주고 싶은 글
{ result.error ? "에러입니다."}
import { useQuery } from "react-query" 한 뒤
let result = useQuery("name", ()=>
axios.get("").then((res)=> { return res.data }),
//최소 간격을 2초로 지정한다.
{staleTime : 2000}
)
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
console.log(error, query);
if (query.state.data !== undefined) {
toast.error(`에러 : ${error.message}`);
},
},
onSuccess: data => {
console.log(data)
}
})
});
mutation 함수가 성공할 때, unique key로 맵핑된 get 함수를 invalidateQueries에 넣어주면 된다.
const mutation = useMutation(postTodo, {
onSuccess: () => {
// postTodo가 성공하면 todos로 맵핑된 useQuery api 함수를 실행
queryClient.invalidateQueries("todos");
}
});