useQuery, useMutation 문서 읽기
리액트 쿼리는 데이터를 캐싱하고 신선하게 유지될 수 있도록 서버의 상태와 동기화해주는 작업을 한다.
기존의 상태관리 라이브러리들은 로컬의 전역 상태를 쉽게 다룰 수 있도록 만들어졌다면 react-query는 서버의 상태에 집중한다. (대부분의 데이터를 서버에서 받아오는 것을 생각한다면 나는 사실 이쪽이 상태관리라는 것엔 더 적합하다고 생각한다.)
서버의 데이터를 캐싱할 때는 그 데이터가 stale하진 않은지 잘 확인하는 과정이 꼭 필요하다. 그렇지 않으면 사용자는 old 데이터만 계속 확인하게 된다.
리액트 쿼리는 이런 과정을 간편하게 사용할 수 있게 만들어 두었고 과한 보일러플레이트 없이 바로 사용할 수 있다. 각 쿼리에 대한 처리와 상태를 가져오는 것은 물론이고 QueryClient
를 이용하면 전체 쿼리와 캐시되어있는 값에 대한 상태를 확인할 수 있다.
isLoading 등과 같은 사용자 경험을 향상시킬 수 있는 상태도 규격화하여 제공한다
react-query를 사용함에 있어서 유의해야할 것
staleTime
값을 설정해주면 된다.staleTime
을 설정하거나 창이 다시 포커싱되면 요청을 새로 보내는 대신 캐싱되어있는 값을 가져와 사용한다.cacheTime
으로 설정 가능retry
값이나 retryDelay
값을 지정react-query hook은 크게 두 가지로 나뉜다
HTTP에서 GET 요청을 다룰 때 사용. 서버에서 값을 받아오기만 할 때
{status, isLoading, isError, isSuccess, isIdle, error, **data**, isFetching}
(etc…)‘data’
)staleTime
의 값을 global or per-query마다 설정해주는 것이 필요하다.const info = useQuery('data', () => axios.get(url));
```tsx
function Todos({ status, page }) {
const result = useQuery(['todos', { status, page }], fetchTodoList);
}
// Access the key, status and page variables in your query function!
function fetchTodoList({ queryKey }) {
const [_key, { status, page }] = queryKey;
return new Promise();
}
```
{queryKey, queryFn, ...config}
객체로 전달 할 수도 있다.하나의 컴포넌트에서 여러 useQuery를 쓰고 싶다면 쓰면 된다. parallel하게 동작
https://react-query-v3.tanstack.com/reference/useQuery 전체 config option은 api문서에서 다룬다.
config
에 enabled 값을 설정해주면 enabled가 true가 될 때까지 query가 실행되지 않는다const result = useQuery({
queryKey: 'key',
queryFn: () => {},
{
enabled: !!이전값이존재하는지,
}
});
refetchOnWindowFocus
에 false
를 지정해주면 된다.focusManager.setEventListener(콜백함수)
를 전달해줄 수 있다. iframe을 이용할 때 window focusing이 두 번 발생하게 되는 상황을 제어할 수 있다. 하지만 이 이벤트 리스너도 alert()
나 <input type=’file’>
로 인한 창을 껐음에도 focusing되어 refetcing 될 수 있다. file upload의 경우 의도와는 다르게 두 번 요청될 수도 있다는 것. 관련이슈staleTime
을 설정해주지 않으면 바로 다시 refetchinitialDataUpdatedAt
에 staleTime을 추가로 지정해서 initial data에 대한 처리를 앞당길 수 있다. 다른 캐시를 이용하여 initial data를 지정할 때 유리HTTP에서 POST, DELETE 등 서버에 변화를 발생시키는(side effect) 작업
{isIdle, isLoading, isError, isSuccess, status, error, data, **mutate**}
(etc…)const { mutate } = useMutation(
(value) => axios.post(url, { value }),
);
...
mutate({name: "kim"});
queryClient.invalidateQueries(쿼리 키)
로 만들어서 이후 해당 키에 대한 요청을 했을 때 캐시를 사용하는 것이 아니라 새로운 값을 받아올 수 있도록 한다.queryClient.setQueryData(key, data)
로 cache를 업데이트 할 수도 있다.보통 서버에 side effect를 발생 시킬 때는 client 단에 있는 캐시를 무효화 시키는 방법을 많이 쓴다. 그러다보니 반복적인 캐시 무효화 코드를 줄이기 위해 아래와 같이 custom hook을 작성해서 쓰는 경우가 많다
const useMutationCustom = () => useMutation(
어떠한비동기작업콜백,
{
onSuccess: () => queryClient.invalidateQueries(쿼리 키);
}
);
///------------------
const { mutate } = useMutationCustom();
...
QueryClient 객체를 사용할 수 있다. new QueryClient와 다른 것은 현재 query client 객체를 반환한다는 것, 즉 위에서 provide해준 queryClient 객체를 사용할 수 있다.
한 번에 여러개의 요청을 보내고 싶을 때 사용
쿼리 객체를 배열로 전달하며 쿼리 결과가 배열로 반환되는 것만 제외하면 useQuery와 동일
query params를 이용하여 데이터를 계속 불러오는 경우 사용할 수 있으며 이를 활용해서 무한 스크롤과 pagination이 가능하게 된다.
현재 페이지의 query params를 나타내는 pageParams
와
다음페이지, 이전 페이지의 query params를 받아오는 getNext/PreviousPageParam
를 이용하여 쿼리가 포함된 api요청을 보내게 된다
https://react-query-v3.tanstack.com/reference/useInfiniteQuery
const {fetchNextPage, fetchPreviousPage} = useInfiniteQuery(
key,
({pageParams = 0}) => params를이용한어떠한비동기호출(pageParams),
{
getNextPageParam: (lastPage, allPages) => fetchNextPage호출시 사용할 api 파라미터,
getPreviousPageParam: (firstPage, allPages) => fetchPreviousPage호출시 사용할 api 파라미터,
}
);
개별 요청에 대한 indicator가 아니라 화면 전체에 대한 global indicator가 필요할 경우 useIsFetching
hook을 이용해서 하나의 query라도 fetching상태에 있는지를 확인하여 나타낼 수 있다.
react-query의 기본 객체(처럼 보인다). useQuery와 useMutation는 사용하는 방법을 좀 더 규격화해둔 느낌
각 component에서 query client instance를 만들어서 사용하는 것도 가능하지만
가장 상위의 컴포넌트에서 global config를 정의하여 <QueryClientProvider client={인스턴스}>
를 통해 아래로 전달해주는 형태로도 사용한다. 이때 하위 컴포넌트에서 hook을 이용하여 인스턴스에 접근할 경우 (useQuery, useMutation, useQueryClient) global config가 적용된다.
const queryClient = new QueryClient({
defaultOption: {
queries: {
staleTime: Infinity,
},
mutations: {
onSuccess: () => console.log('success!')
}
}
});
...
<QueryClientProvider client={queryClient}>
...
</QueryClientProvider>
getQueryCache
, getMutationCache
로 각 캐시 값에 접근할 수도 있다.
메소드 중 가장 많이 쓰일 것 같다고 생각한건 위에서도 말한
QueryClient.invalidateQueries
캐시가 stale하다는 것을 잘 전달해줘야 데이터를 확실하게 잘 받아올 수 있을 것
https://react-query-v3.tanstack.com/reference/QueryClient
react에서 제공하는 error boundary 와 suspense를 바로 사용할 수 있다. status나 error 객체를 전달하는 것과는 관계 없이 바로 가장 가까운 error boundary로 전파된다.
error boundary를 reset하고 싶다면 <QuryErrorResetBoundary>
로 전체를 감싸 reset을 <ErrorBoundary>
에의 onReset에게 전달해주거나 useQueryErrorResetBoundary()
훅을 이용할 수도 있다.
<QueryErrorResetBoundary>
{({ reset })=>
<ErrorBoundary onReset={reset} ... >
...
</ErrorBoundary>
}
</QueryErrorResetBoundary>
// 또는
const { reset } = useQueryErrorResetBoundary();
...
<ErrorBoundary onReset={reset} ... >
...
</ErrorBoundary>
하지만 fetch on render(렌더링 하면서 데이터가 필요하면 패칭을 함 → 각 비동기 작업들이 병렬로 수행X)는 추가적인 config없이 잘 작동하지만 render as you fetch(데이터를 패치하는 것에 따라 render, 되는대로 렌더링 하겠다는 것)로 구현하고자 한다면 각 렌더링을 시작하는 상호작용 이벤트에 대해 prefetching을 미리 설정해주는 것이 좋다.
https://react-query-v3.tanstack.com/guides/ssr
ssr에서 데이터를 채워서 넘어와야하는 경우 initial data를 통해 우선적으로 렌더링을 진행한 다음 client에 왔을 때 다시 cache를 rehydrate 하여 작동할 수 있도록 한다.
특히 next js의 경우는 getStaticProps 내부에서 첫 비동기 작업을 시행하도록 한 뒤 그 값을 props로 넘겨서 initialData
로 지정할 수 있다
export async function getStaticProps() {
const posts = await getPosts();
return { props: { posts } };
}
function Posts({ posts }) {
const { data } = useQuery('posts', getPosts, { initialData: posts });
...
}