프로젝트에서 @tanstack/react-query를 v4 쓰고 있다가 이번에 v5로 변경하였다.
제일 큰 변화점은 v5부터는 객체 형식만 지원한다는 점이다.
참고
https://tanstack.com/query/v5/docs/react/reference/QueryClient
https://github.com/ssi02014/react-query-tutorial/blob/main/document/v5.md
https://wonsss.github.io/library/tanstack-query-v5/
https://www.moonkorea.dev/React-TanStack-Query-v5-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-(%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%BF%BC%EB%A6%AC)
v4에서는 useQuery(key, fn, options)
, useQuery({queryKey, queryFn, ...options})
두 형태를 모두 지원했는데 이는 유지보수가 힘들고, 매개 변수 타입을 확인하기 위한 런타임 검사도 필요했기 때문에 오로지 객체 형식만 지원하도록 v5에서 변경되었다.
queryClient.getQueryData, queryClient.getQueryState의 인수가 queryKey만 받도록 v5에서 수정되었다.
queryClient.getQueryData(queryKey)
queryClient.getQueryState(queryKey)
Callbacks 함수의 onSuccess
, onError
, onSettled
가 제거되었다.
remove 메서드가 제거되었다.
쿼리를 제거해야하는 경우 queryClient.removeQueries({ queryKey: key })
를 사용한다.
isDatEqual을 사용하지 않고 동일한 기능인 structuralSharing
으로 활용
cacheTime
이 gcTime
으로 변경되었다.
useErrorBoundary
옵션은 throwOnError
로 이름이 변경됩니다. 리액트 훅의 접두사인 "use"와 특정 컴포넌트명인 "ErrorBoundary"의 사용보다는 옵션이 제공하는 기능에 맞게 다음 렌더 사이클에 에러를 다시 던지는 throwOnError로 변경됩니다.
error의 기본 타입이 Error
입니다. 거의 모든 경우에 Error 타입을 갖기 때문에 v5부터 error 필드는 Error 타입으로 추론됩니다.
커스텀 에러를 활용하거나 Error가 아닌 것을 활용하고 싶다면 아래 예제처럼 타입을 구체화할 수 있습니다.
const { error } = useQuery<Group[], string>({
queryKey: ["groups"],
queryFn: fetchGroups,
});
keepPreviousData
옵션과 isPreviousData
는 placeholderData
옵션과 isPlaceholderData
로 변경됩니다. v5에서 keepPreviousData는 리액트 쿼리에서 제공하는 함수(identity function)로 변경되는데요, 모듈에 불러와 placeholderData의 값으로 사용합니다.
useQuery({
queryKey,
queryFn,
placeholderData: (previousData, previousQuery) => previousData,
// identity function with the same behaviour as `keepPreviousData`
});
Tanstack Query는 visibilitychange
이벤트를 지원하는 브라우저만 지원하도록 결정됐습니다. 따라서, 이제 visibilitychange 이벤트만 독점적으로 사용됩니다.
커스텀 queryClient 인스턴스를 위해 커스텀 context
prop이 제거되었습니다.
refetchpage
를 제거하고 maxPages
가 추가 되었습니다.
infinite Query
옵션에 명시적인 initialPageparam
을 전달해야 합니다.
이전 버전에서는 queryFn의 pageParam이 undefined 값을 가져서 0 또는 초기 값을 정의했었는데 undefined는 직렬화되지 않아 initialPageParam 옵션이 추가됐습니다.
Infinite query
를 사용할 때 pageParam
의 초기 값으로 사용될 initialPageParam
옵션을 전달해야 합니다. 이전 버전에서는 queryFn의 pageParam이 undefined 값을 가져서 0 또는 초기 값을 정의했었는데 undefined는 직렬화되지 않아 initialPageParam 옵션이 추가됐습니다.
v4에서는 더 이상 페이지 없음을 나타내기 위해 명시적으로 undefined를 반환해야 했습니다. v5부터는 undefined
뿐만 아니라 null
까지 포함하도록 확장됐습니다.
서버에서의 retry
기본 값은 3
에서 0
으로 변경됩니다.
status의 loading
은 pending
으로 변경됩니다.
isLoading
은 isPending
으로 변경됩니다.
isPending && isFetching
의 기능인 isInitialLoading
은 isLoading
으로 변경됩니다.
v5부터는 낙관적 업데이트를 수행하는 방법을 제공합니다.
const queryInfo = useTodos()
const addTodoMutation = useMutation({
mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})
if (queryInfo.data) {
return (
<ul>
{queryInfo.data.items.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
{addTodoMutation.isPending && (
<li key={String(addTodoMutation.submittedAt)} style={{ opacity: 0.5 }}>
{addTodoMutation.variables}
</li>
)}
</ul>
)
}
위 예제에서는 데이터를 캐시에 직접 쓰는 대신에 mutation이 실행중일 때 UI가 표시되는 방식만 변경합니다. 해당 방법은 낙관적 업데이트를 표시해야 하는 위치가 한 곳만 있는 경우에 효과적입니다
infinite queries
도 normal queries
처럼 prefetch
할 수 있습니다. 기본으로 한 개 페이지에 대한 쿼리를 prefetch 하지만 pages 옵션과 getNextPageParam
옵션으로 한 개 이상의 페이지를 prefetch 할 수 있습니다.
const prefetchTodos = async () => {
// The results of this query will be cached like a normal query
await queryClient.prefetchInfiniteQuery({
queryKey: ["projects"],
queryFn: fetchProjects,
initialPageParam: 0,
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
pages: 3, // prefetch the first 3 pages
});
};
useQueries의 combine
으로 응답(쿼리에 대한 정보 등)을 하나의 값으로 사용할 수 있습니다.
다만 위의 경우 쿼리의 data와 pending 값만 반환되고 쿼리에 대한 나머지 정보는 유실됩니다.
data fetching에 대한 suspense
가 안정화가 되었습니다.
useQuery에서 사용하던 suspense: boolean
옵션은 제거되고 useSuspenseQuery
, useSuspenseInfiniteQuery
와 useSuspenseQueries
이 추가되었습니다.
suspense와 관련된 자세한 내용은 suspense를 참고해주시길 바랍니다.
Tanstack Query v5는 필요한 TypeScript
최소 버전이 v4.7
입니다.
Tanstack Query v5는 필요한 React
최소 버전이 v18.0
입니다. 이는 React v18 이상에서만 사용할 수 있는 useSyncExternalStore
훅을 사용하고 있기 때문입니다.
리액트 쿼리는 최신 브라우저에 최적화되어 있습니다.
Chrome >= 91
Firefox >= 90
Edge >= 91
Safari >= 15
iOS >= 15
opera >= 77