react-query
는 서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러 핸들링 등 비동기 과정을 더욱 편하게 하는데 사용된다.react-query
를 사용함으로 서버, 클라이언트 데이터를 분리한다.yarn install react-query
// src/index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
const queryClient = new QueryClient();
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
{/* devtools */}
<ReactQueryDevtools initialIsOpen={true} />
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
enabled
를 사용하면 useQuery를 동기적으로 사용 가능하다.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를 날린다.
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>
);
};
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를 실행한다
}
);
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 ...}]
const loadingFinishAll = result.some(result => result.isLoading);
console.log(loadingFinishAll); // loadingFinishAll이 false이면 최종 완료
}, [result]);
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)
}
})
});
import { useState, useContext, useEffect } from "react";
import loginApi from "api";
import { useMutation } from "react-query";
const Index = () => {
const [id, setId] = useState("");
const [password, setPassword] = useState("");
const loginMutation = useMutation(loginApi, {
onMutate: variable => {
console.log("onMutate", variable);
// variable : {loginId: 'xxx', password; 'xxx'}
},
onError: (error, variable, context) => {
// error
},
onSuccess: (data, variables, context) => {
console.log("success", data, variables, context);
},
onSettled: () => {
console.log("end");
}
});
const handleSubmit = () => {
loginMutation.mutate({ loginId: id, password });
};
return (
<div>
{loginMutation.isSuccess ? "success" : "pending"}
{loginMutation.isError ? "error" : "pending"}
<input type="text" value={id} onChange={e => setId(e.target.value)} />
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
<button onClick={handleSubmit}>로그인</button>
</div>
);
};
export default Index;
invalidateQueries
에 넣어주면 된다.const mutation = useMutation(postTodo, {
onSuccess: () => {
// postTodo가 성공하면 todos로 맵핑된 useQuery api 함수를 실행한다.
queryClient.invalidateQueries("todos");
}
});
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"
});
QueryClient
에 옵션을 하나 추가한다.(global하게 suspense를 사용한다고 가정할 때)// src/index.js
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 0,
suspense: true
}
}
});
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
const { data } = useQurey("test", testApi, { suspense: true });
const { data } = useQurey("test", testApi, { suspense: true });
return (
// isLoading이 true이면 Suspense의 fallback 내부 컴포넌트가 보여진다.
// isError가 true이면 ErrorBoundary의 fallback 내부 컴포넌트가 보여진다.
<Suspense fallback={<div>loading</div>}>
<ErrorBoundary fallback={<div>에러 발생</div>}>
<div>{data}</div>
</ErrorBoundary>
</Supense>
);
참조:
react-query