서버의 값을 클라이언트에 가져오거나, 캐싱, 값 업데이트, 에러핸들링 등 비동기 과정을 더욱 편하게 하는데 사용
서버, 클라이언트 데이터를 분리
주로 아래와 같이 프론트 개발자가 구현하기 귀찮은 일들을 수행
캐싱
get을 한 데이터에 대해 update를 하면 자동으로 get을 다시 수행
데이터가 오래 되었다고 판단되면 다시 get (invalidateQueries
)
동일 데이터 여러번 요청하면 한번만 요청 (옵션에 따라 중복 호출 허용 시간 조절 가능)
무한 스크롤 (Infinite Queries (opens new window))
비동기 과정을 선언적으로 관리 가능
react hook와 사용 구조 비슷
yarn install react-query
src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { QueryClient, QueryClientProvider } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'
const queryClient = new QueryClient()
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={true} />
<App />
</QueryClientProvider>
</React.StrictMode>
)
데이터를 get 하기 위한 api
post, update
useMutation
사용
return 값
객체 (api의 성공, 실패여부, api return 값을 포함)
첫번째: unique Key
unique Key
다른 컴포넌트에서도 해당 키를 사용 시, 호출 가능.
string과 배열을 받음.
배열로 넘기면 0번 값: string으로 다른 컴포넌트에서 부를 값이 들어감.
두번째 값을 넣으면 query 함수 내부에 파라미터로 해당 값이 전달됨.
두번째: 비동기 함수(api호출 함수. promise가 들어감.)
refetchOnWindowFocus
사용자가 사용하는 윈도우가 다른 곳을 갔다가 다시 화면으로 돌아오면 이 함수를 재실행.
그 재실행 여부 옵션
retry
실패 시, 재호출 몇 번 할지
onSuccess
성공 시 호출
onError
api 호출이 실패한 경우만 호출 (401, 404같은 error가 아님)
status
로 한번에 처리 가능
isLoading, isSuccess 말고
예시
기본
import { useQuery } from 'react-query'
import { fetchTodoList } from '../services/todo'
const Todos = () => {
const { isLoading, isError, data, error } = useQuery("todos", fetchTodoList, {
refetchOnWindowFocus: false,
retry: 0,
onSuccess: data => {
console.log(data);
},
onError: e => {
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>
);
}
즉, 한 컴포넌트에 여러 개의 useQuery가 있다면 동시 실행함.
단, 여러 개의 비동기 query가 있다면 useQueries
권유.
enabled
사용
useQuery의 옵션값(3번째 인자)에 enabled에 값을 넣으면 그 값이 true일때 useQuery 실행. 이것을 이용해서 동기적으로 함수 실행
예시
const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
const { data: nextTodo, error, isFetching } = useQuery(
"nextTodos",
fetchNextTodoList,
{
enabled: !!todoList // true가 되면 fetchNextTodoList 실행
}
);
useQuery를 하나로 묶기 가능.
사용법
하나의 배열에 각 쿼리에 대한 상태 값이 객체로 들어옴.
예시
useQuery
어짜피 세 함수 모두 비동기로 실행하는데, 세 변수를 개발자는 다 기억해야하고 세 변수에 대한 로딩, 성공, 실패처리를 모두 해야 함.
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]);
unique key를 배열로 넣으면 query함수 내부에서 변수로 사용 가능. 그것을 활용.
// params를 주목
const riot = {
version: "12.1.1"
};
const result = useQueries([
{
queryKey: ["getRune", riot.version],
queryFn: params => {
console.log(params); // 👉 {queryKey: ['getRune', '12.1.1'], pageParam: undefined, meta: undefined}
return api.getRunInfo(riot.version);
}
},
{
queryKey: ["getSpell", riot.version],
queryFn: () => api.getSpellInfo(riot.version)
}
]);
쿼리에 대해 성공, 실패 전처리 가능
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)
}
})
});
값을 바꿀때 사용하는 api
return 값
useQuery와 동일
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;
mutation 함수가 성공할 때, unique key로 맵핑된 get 함수를 invalidateQueries
에 넣기
const mutation = useMutation(postTodo, {
onSuccess: () => {
// postTodo 성공 시, todos로 맵핑된 useQuery api 함수를 실행
queryClient.invalidateQueries("todos");
}
});
만약 mutation에서 return된 값을 이용해서 get 함수의 파라미터를 변경해야할 경우 사용
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"
});
react-query 사용 이유 中 1
비동기를 더 선언적으로 사용 가능
더욱 직관적으로
loading → Suspense (opens new window)를 사용
에러 핸들링 → Error buundary (opens new window)를 사용
사용 방법
QueryClient
에 옵션 하나 추가.
예시
글로벌하게 사용
// 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 });
세팅 완료 시, 사용
return (
// isLoading이 true이면 Suspense의 fallback 내부 컴포넌트가 보임
// isError가 true이면 ErrorBoundary의 fallback 내부 컴포넌트가 보임
<Suspense fallback={<div>loading</div>}>
<ErrorBoundary fallback={<div>에러 발생</div>}>
<div>{data}</div>
</ErrorBoundary>
</Supense>
);
참고