Next.js 에 React Query 를 도입하며, 관련된 사용법과 옵션을 기록하기 위한 글입니다.
데이터 fetching 에 쓰이는 Hook. GET 메소드 사용시에 자주 쓰인다.
QueryKey
를 기반으로 데이터 캐싱을 관리한다.// 문자열
useQuery('todos', ...)
// 배열
useQuery(['todos'], ...)
const { data, isLoading, error } = useQuery(['todos', id], () => axios.get(`http://.../${id}`));
// 방법 1
useQuery('todos', fetchTodos);
// 방법 2
useQuery(['todos', todoId], () => fetchTodoById(todoId));
number
밀리세컨즈 후 stale 상태로 처리할 것인지 설정한다. (default : 0)number
millisecond 동안 캐시 데이터가 메모리에 남아있게 됩니다. 이 이후 가비지 컬렉션에서 이 데이터를 처리합니다. (default: 5 60 1000 => 5 min)const { data, isFetching } = useQuery([page, searchValue], fetcher, {
staleTime: 60 * 1000,
keepPreviousData: true,
})
서버 데이터 업데이트할 때 쓰이는 Hook
데이터 생성/수정/삭제 시 자주 쓰인다.
다음과 같이 post, put 등 서버 데이터에 변경이 발생할 때 사용되는 Hook 이 useMutation 이다.
const mutation = useMutation(newTodo => axios.post('/todos', newTodo))
const handleSubmit = useCallback(
(newTodo) => {
mutation.mutate(newTodo)
},
[mutation],
)
하지만, useMutation 의 경우 API 콜 후에 서버의 response 를 data 에서 받아올 수 없다는 단점이 있다.
공식문서에서도 useMutation 의 return 값은 undefined | unknown
으로 명시되어 있다. 만약 useMutation 사용 시 reponse 가 필요한 경우라면, mutateAsync
로 얻어올 수 있다.
mutateAsycn 는 Promise 를 return 하게 되고, Promise result 로 response 를 가져올 수 있다.
const postTodo = (todo) => {
axios.post('/api/data', { todo })
}
const createTodo = useMutation(postTodo);
createTodo.mutateAsync(todo).then((data) => {
console.log(data);
// console로 찍은 data가 서버의 response 값입니다.
});
React Query는 SSR 을 두가지 방식으로 구현할 수 있다. initalData 를 주는 방법과, Hydration 을 하는 방법.
initialData 를 통한 방법에서는 데이터를 명시해주기만 하면 되기 때문에 훨씬 간단하지만, 만약 여러 컴포넌트에서 해당 데이터를 SSR 을 통해 사용자에게 보여준다고 하면 모든 컴포넌트에 initialData 를 넘겨줘야 하는 문제가 있다. 컴포넌트의 뎁스가 깊어질 수록 비효율적이다.
반면 Hydration 을 통한 방법은 SSR 을 할 때, 원하는 쿼리를 prefetch 하고 해당 쿼리를 사용하는 컴포넌트에서는 동일한 키로 useQuery 훅을 호출하기만 하면 ****
// pages/poke.tsx
import Pokemon from '../components/Pokemon'
import { getPoke } from '../api'
import { QueryClient } from 'react-query'
import { dehydrate } from 'react-query/hydration'
const Poke = () => {
return (
<Pokemon />
)
}
export async function getServerSideProps() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery('poke',
async() => {
// Next.js 에서 data 안꺼내주면 serializable 에러 뜸.
const {data} =getPoke();
return data;
},
{ staleTime: 1000 })
return {
props: {
dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))),
}
}
}
export default Poke
이후로 getServerSideProps 에서 props 로 넘겨준, dehydratedState를 _app 에서 받아 Hydration 으로 내려줄 것이다. 이제 컴포넌트에서 prefetch 에서 사용된 쿼리와 같은 키인 poke 를 사용해, useQuery 훅을 호출하면 된다.
const { data, isFetching } = useQuery('poke',
() => getPoke(),
{
staleTime: 1000,
}
)
⭐ 만약 custom hook 을 만든다면?
export function useGetWorst10 (startDate:string, endDate:string, workingHour:string){
return useQuery(["worst10", startDate, endDate, workingHour],
async()=> {
const {data}= await Worst10API.yulkok( startDate, endDate, workingHour);
return data.results;
},
{
staleTime: 600*1000,
keepPreviousData:true
}
)
}
useInfiniteQuery 란 파라미터 값만 변경해, 동일한 useQuery 를 무한정 호출할 때 사용됩니다.
보통 Infinite Scroll 을 구현할 때, 많이 사용된다.
const {
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
...result
} = useInfiniteQuery(queryKey, ({ pageParam = 1 }) => fetchPage(pageParam), {
...options,
getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})
const useBlacklistQuery = () => {
// useInfiniteQuery에서 쓸 함수
const fetchBlacklist = async ({ pageParam = 1 }) => {
const response = await axiosInstance.get(
`/api/---/${pageParam}`,
);
const result = response.data;
// axios로 받아온 데이터를 다음과 같이 변경!
return {
result: result.blacklist,
nextPage: pageParam + 1,
isLast: result.is_last,
};
};
const query = useInfiniteQuery('[blacklist]', fetchBlacklist, {
getNextPageParam: (lastPage, pages) => {
if (!lastPage.isLast) return lastPage.nextPage;
return undefined;
},
refetchOnWindowFocus: false,
refetchOnMount: true,
refetchOnReconnect: true,
retry: 1,
});
return query;
};
result 의 구조 예시
result : {
blacklist : {
user1 : {
},
user2 : {
},
},
is_last : true, // 마지막 여부
}
🟢 React Query 는 기존 상태관리 라이브러리에서 요구하는 boilerplate 코드를 제거할 수 있다.
🟢 캐싱 & 리프레시
🔴 코드 구조를 잘 고민해야 한다.