LifeCycle(라이프 사이클)
React Query를 통해 관리되는 쿼리 데이터는 라이프 사이클에 따라 각 상태를 가진다.
fetching : 요청중인 쿼리
fresh : 만료되지 않은 쿼리. 컴포넌트가 마운트, 업데이트 되어도 데이터를 재요청하지 않음
stale : 만료된 쿼리. 컴포넌트가 마운트, 업데이트되면 데이터를 다시 요청한다
inactive : 사용하지 않는 쿼리. 일정 시간이 지나면 가비지 컬렉터가 캐시에서 제거
delete : 캐시에서 제거된 쿼리.
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<QueryClientProvider client={queryClient}>
<React.StrictMode>
<App />
</React.StrictMode>
</QueryClientProvider>
);
reportWebVitals();
react-query세팅!!
useQuery
데이터를 get 하기 위한 api다. post, update는 useMutation을 사용한다.
첫 번째 파라미터로 unique Key가 들어가고, 두번째 파라미터로 비동기 함수(api 호출 함수)가 들어간다. (당연한 말이지만 두번째 파라미터는 promise가 들어가야 한다.)
첫번째 파라미터로 설정한 unique Key는 다른 컴포넌트에서도 해당 키를 사용하면 호출 가능하다 unique Key는 string과 배열을 받는다. 배열로 넘기면 0번 값은 string 값으로 다른 컴포넌트에서 부를 값이 들어가고 두 번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달된다.
return 값은 api의 성공, 실패 여부, api return 값을 포함한 객체이다.
useQuery는 비동기로 작동한다. 즉, 한 컴포넌트에 여러 개의 useQuery가 있다면 하나가 끝나고 다음 useQuery가 실행되는 것이 아닌 두개의 useQuery가 동시에 실행된다. 여러개의 비동기 query가 있다면 useQuery보다는 밑에 설명할 useQuries를 추천한다.
enabled를 사용하면 useQeury를 동기적으로 사용 가능하다.
const Todos = () => {
const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
refetchOnWindowFocus: false, // react-query는 사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행합니다. 그 재실행 여부 옵션 입니다.
retry: 0, // 실패시 재호출 몇번 할지
onSuccess: data => {
// 성공시 호출
console.log(data);
},
onError: e => {
// 실패시 호출 (401, 404 같은 error가 아니라 정말 api 호출이 실패한 경우만 호출됩니다.)
// 강제로 에러 발생시키려면 api단에서 throw Error 날립니다. (참조: https://react-query.tanstack.com/guides/query-functions#usage-with-fetch-and-other-clients-that-do-not-throw-by-default)
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>
);
};
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>
);
}
useQueries
비동기로 여러번 실행하는 경우
const usersQuery = useQuery("users", fetchUsers);
const teamsQuery = useQuery("teams", fetchTeams);
const projectsQuery = useQuery("projects", fetchProjects);
// 어차피 세 함수 모두 비동기로 실행하는데, 세 변수를 개발자는 다 기억해야하고 세 변수에 대한 로딩, 성공, 실패처리를 모두 해야한다.
useQueries로 한번에 해결하기
// 아래 예시는 롤 룬과, 스펠을 받아오는 예시
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 ...}]
const loadingFinishAll = result.some(result => result.isLoading);
console.log(loadingFinishAll); // loadingFinishAll이 false이면 최종 완료
}, [result]);
변경후에 get 다시 실행
react-query 장점으로 변경 후에 get 함수를 간단히 재실행할 수 있다고 한다.
mutation 함수가 성공할 때, unique key로 맵핑된 get 함수를 invalidateQueries에 넣어주면 된다.
const mutation = useMutation(postTodo, {
onSuccess: () => {
// postTodo가 성공하면 todos로 맵핑된 useQuery api 함수를 실행합니다.
queryClient.invalidateQueries("todos");
}
});
만약 mutation에서 return 된 값을 이용해서 get 함수의 파라미터를 변경해야 할 경우 setQueryData를 사용한다.
const queryClient = useQueryClient();
const mutation = useMutation(editTodo, {
onSuccess: data => {
// data가 fetchTodoById로 들어간다
queryClient.setQueryData(["todo", { id: 5 }], data);
}
});
const { status, data, error } = useQuery(["todo", { id: 5 }], fetchTodoById);
mutation.mutate({
id: 5,
name: "nkh"
});