Skip to content

Latest commit

ย 

History

History
510 lines (386 loc) ยท 20.5 KB

File metadata and controls

510 lines (386 loc) ยท 20.5 KB

๐Ÿ’ป Migrating to TanStack Query(React) v5

๐Ÿ“„ ์ฃผ์š” ๋ณ€๋™ ์‚ฌํ•ญ (โญ๏ธ ์ค‘์š”)

1. โญ๏ธ Supports a single signature, one object

  • useQuery, useInfiniteQuery, useMutation์ด ์ด์ œ๋Š” ๊ฐ์ฒด ํ˜•์‹๋งŒ ์ง€์›ํ•˜๋„๋ก ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • v4์—์„œ๋Š” useQuery(key, fn, options), useQuery({ queryKey, queryFn, ...options }) ๋‘ ํ˜•ํƒœ๋ฅผ ๋ชจ๋‘ ์ง€์›ํ–ˆ๋Š”๋ฐ ์ด๋Š” ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ํž˜๋“ค๊ณ , ๋งค๊ฐœ ๋ณ€์ˆ˜ ํƒ€์ž…์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ ๋Ÿฐํƒ€์ž„ ๊ฒ€์‚ฌ๋„ ํ•„์š”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋กœ์ง€ ๊ฐ์ฒด ํ˜•์‹๋งŒ ์ง€์›ํ•˜๋„๋ก v5์—์„œ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
- useQuery(key, fn, options)
+ useQuery({ queryKey, queryFn, ...options })
- useInfiniteQuery(key, fn, options)
+ useInfiniteQuery({ queryKey, queryFn, ...options })
- useMutation(fn, options)
+ useMutation({ mutationFn, ...options })
- useIsFetching(key, filters)
+ useIsFetching({ queryKey, ...filters })
- useIsMutating(key, filters)
+ useIsMutating({ mutationKey, ...filters })
- queryClient.isFetching(key, filters)
+ queryClient.isFetching({ queryKey, ...filters })
- queryClient.ensureQueryData(key, filters)
+ queryClient.ensureQueryData({ queryKey, ...filters })
- queryClient.getQueriesData(key, filters)
+ queryClient.getQueriesData({ queryKey, ...filters })
- queryClient.setQueriesData(key, updater, filters, options)
+ queryClient.setQueriesData({ queryKey, ...filters }, updater, options)
- queryClient.removeQueries(key, filters)
+ queryClient.removeQueries({ queryKey, ...filters })
- queryClient.resetQueries(key, filters, options)
+ queryClient.resetQueries({ queryKey, ...filters }, options)
- queryClient.cancelQueries(key, filters, options)
+ queryClient.cancelQueries({ queryKey, ...filters }, options)
- queryClient.invalidateQueries(key, filters, options)
+ queryClient.invalidateQueries({ queryKey, ...filters }, options)
- queryClient.refetchQueries(key, filters, options)
+ queryClient.refetchQueries({ queryKey, ...filters }, options)
- queryClient.fetchQuery(key, fn, options)
+ queryClient.fetchQuery({ queryKey, queryFn, ...options })
- queryClient.prefetchQuery(key, fn, options)
+ queryClient.prefetchQuery({ queryKey, queryFn, ...options })
- queryClient.fetchInfiniteQuery(key, fn, options)
+ queryClient.fetchInfiniteQuery({ queryKey, queryFn, ...options })
- queryClient.prefetchInfiniteQuery(key, fn, options)
+ queryClient.prefetchInfiniteQuery({ queryKey, queryFn, ...options })
- queryCache.find(key, filters)
+ queryCache.find({ queryKey, ...filters })
- queryCache.findAll(key, filters)
+ queryCache.findAll({ queryKey, ...filters })

2. โญ๏ธ 'queryClient.getQueryData', 'queryClient.getQueryState' now accepts queryKey only as an Argument

  • queryClient.getQueryData์˜ ์ธ์ˆ˜๊ฐ€ queryKey๋งŒ ๋ฐ›๋„๋ก v5์—์„œ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
- queryClient.getQueryData(queryKey, filters)
+ queryClient.getQueryData(queryKey)
  • ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ queryClient.getQueryState๋„ ์ธ์ˆ˜๊ฐ€ queryKey๋งŒ ๋ฐ›๋„๋ก v5์—์„œ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
- queryClient.getQueryState(queryKey, filters)
+ queryClient.getQueryState(queryKey)

3. โญ๏ธ Callbacks on useQuery (and QueryObserver) have been removed

  • useQuery์˜ ์˜ต์…˜์ธ onSuccess, onError, onSettled๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
    • ํ•ด๋‹น ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋“ค์€ ๊ฐ„๋‹จํ•˜๊ณ , ์ง๊ด€์ ์ด๋ผ ๊ต‰์žฅํžˆ ์œ ์šฉํ•˜์ง€๋งŒ ๋ฒ„๊ทธ๋ฅผ ์œ ๋ฐœ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ž์„ธํ•œ ๋‚ด์šฉ์€ Tanstack Query maintainer์ธ tkdodo ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŒ…์„ ์ฐธ๊ณ ํ•ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

4. The 'remove' method has been removed from useQuery

  • useQuery์˜ remove ๋ฉ”์„œ๋“œ๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด์ „์—๋Š” remove ๋ฉ”์„œ๋“œ๋Š” observer์—๊ฒŒ ์•Œ๋ฆฌ์ง€ ์•Š๊ณ  ์ฟผ๋ฆฌ๋ฅผ queryCache์—์„œ ์ œ๊ฑฐํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

    • ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๋ฅผ ๋กœ๊ทธ์•„์›ƒ ํ•  ๋•Œ์™€ ๊ฐ™์ด ๋” ์ด์ƒ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐํ•  ๋•Œ์™€ ๊ฐ™์€ ๊ฒฝ์šฐ์— ํ™œ์šฉ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ์˜€์Šต๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ, query๊ฐ€ ์•„์ง ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—์„œ ์ด remove ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋‹ค์Œ ๋ฒˆ ๋ฆฌ๋ Œ๋”๋ง ํ•  ๋•Œ hard loading ์ƒํƒœ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ฉ๋ฆฌ์ ์ด์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.

    • ์—ฌ๊ธฐ์„œ, hard loading ์ƒํƒœ๋ž€? ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ์ฆ‰, ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค.
    • useQuery์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” isLoading์ด ์ด๋Ÿฐ hard loading ์ƒํƒœ์ธ ๊ฒฝ์šฐ์—๋งŒ ์ฐธ(true)์ž…๋‹ˆ๋‹ค.
    • When we refetch a query, it doesn't set isLoading true.
  • ํ•˜์ง€๋งŒ!! ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ฟผ๋ฆฌ๋ฅผ ์ œ๊ฑฐํ•ด์•ผ ๋œ๋‹ค๋ฉด queryClient.removeQueries๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

const queryClient = useQueryClient();
const query = useQuery({ queryKey, queryFn });
- query.remove()
+ queryClient.removeQueries({ queryKey })

5. The 'isDataEqual' option has been removed from useQuery

  • isDataEqual ํ•จ์ˆ˜๋Š” query์—์„œ resolved๋œ ๋ฐ์ดํ„ฐ๋กœ์„œ ์ด์ „ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ• ์ง€ ์•„๋‹ˆ๋ฉด ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ• ์ง€ ํ™•์ธํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ด์ œ๋Š” isDataEqual์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ๋™์ผํ•œ ๊ธฐ๋Šฅ์œผ๋กœ์„œ structuralSharing์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
import { replaceEqualDeep } from '@tanstack/react-query'

- isDataEqual: (oldData, newData) => customCheck(oldData, newData)
+ structuralSharing: (oldData, newData) => customCheck(oldData, newData) ? oldData : replaceEqualDeep(oldData, newData)

6. โญ๏ธ Rename 'cacheTime' to 'gcTime'

  • cacheTime์ด gcTime์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
    • ๋„ค์ด๋ฐ์ด ๋ณ€๊ฒฝ๋œ ์ด์œ ๋Š” ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด cacheTime์„ ๋งˆ์น˜ "๋ฐ์ดํ„ฐ๊ฐ€ ์บ์‹œ๋˜๋Š” ์‹œ๊ฐ„"์œผ๋กœ ์ฐฉ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
    • ํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ query๊ฐ€ ๊ณ„์† ์‚ฌ์šฉ๋˜๋Š” ํ•œcacheTime์€ ์•„๋ฌด ์ผ๋„ ํ•˜์ง€ ์•Š๊ณ , query๊ฐ€ ๋”์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ์‹œ์ ์— ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  cacheTime ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์บ์‹œ๊ฐ€ ๋”์ด์ƒ ์ปค์ง€๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋Š” garbage collected๋ฉ๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ, ์˜๋ฏธ์ƒ์˜ ํ˜ผ๋™์„ ์ค„์ด๊ธฐ ์œ„ํ•ด cacheTime์—์„œ gcTime์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
const MINUTE = 1000 * 60;

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
-      cacheTime: 10 * MINUTE,
+      gcTime: 10 * MINUTE,
    },
  },
})

7. โญ๏ธ The 'useErrorBoundary' option has been renamed to 'throwOnError'

  • ๊ธฐ์กด์— ErrorBoundary์— ์—๋Ÿฌ๋ฅผ ๋˜์ง€๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ–ˆ๋˜ ์˜ต์…˜์ธ useErrorBoundary๋ฅผ ํŠน์ • ํ”„๋ ˆ์ž„์›Œํฌ์— ์ข…์†๋˜์ง€ ์•Š์œผ๋ฉด์„œ, ๋ฆฌ์•กํŠธ ์ปค์Šคํ…€ ํ›…์˜ ์ ‘๋ฏธ์‚ฌ์ธ use์™€ ErrorBoundary ์ปดํฌ๋„ŒํŠธ๋ช…๊ณผ ํ˜ผ๋™์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด, throwOnError๋กœ ๋ณ€๊ฒฝ๋์Šต๋‹ˆ๋‹ค.
const todos = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
-  useErrorBoundary: true,
+  throwOnError: true,
})

8. โญ๏ธ TypeScript: 'Error' is now the default type for errors instead of 'unknown'

  • v5 ๋ถ€ํ„ฐ๋Š” error์˜ ๊ธฐ๋ณธ ํƒ€์ž…์ด Error ์ž…๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ๋œ ์ด์œ ๋Š” ๋งŽ์€ ์‚ฌ์šฉ์ž๋“ค์ด ๊ธฐ๋Œ€ํ•˜๋Š” ๊ฒฐ๊ณผ์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
// const error: Error
const { error } = useQuery({
  queryKey: ["groups"],
  queryFn: fetchGroups,
});
  • ๋งŒ์•ฝ ์ปค์Šคํ…€ ์—๋Ÿฌ๋ฅผ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜ Error๊ฐ€ ์•„๋‹Œ ๊ฒƒ์„ ํ™œ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์•„๋ž˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ ํƒ€์ž…์„ ๊ตฌ์ฒดํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// const error: string | null
const { error } = useQuery<Group[], string>({
  queryKey: ["groups"],
  queryFn: fetchGroups,
});

9. โญ๏ธ Removed 'keepPreviousData' in favor of 'placeholderData' identity function

  • โ€‹keepPreviousData ์˜ต์…˜๊ณผ isPreviousData ํ”Œ๋ž˜๊ทธ๊ฐ€ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
    • ์™œ๋ƒํ•˜๋ฉด ์ด๋“ค์€ ๊ฐ๊ฐ placeholderData์™€ `isPlaceholderData ํ”Œ๋ž˜๊ทธ์™€ ๊ฑฐ์˜ ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • ์•„๋ž˜ ์˜ˆ์ œ๋Š” placeholderData๋ฅผ ํ™œ์šฉํ•˜๋ฉด์„œ ์ด์ „์— keepPreviousData ์˜ต์…˜์„ true๋กœ ์คฌ์„๋•Œ์™€ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด identity function์„ ํ—ˆ์šฉํ•˜๋Š” placeholderData์— Tanstack Query์— ํฌํ•จ๋œ keepPreviousData ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
import {
   useQuery,
+  keepPreviousData
} from "@tanstack/react-query";

const {
   data,
-  isPreviousData,
+  isPlaceholderData,
} = useQuery({
  queryKey,
  queryFn,
- keepPreviousData: true,
+ placeholderData: keepPreviousData
});
  • ๋˜๋Š”, ์ง์ ‘ identity function์„ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
useQuery({
  queryKey,
  queryFn,
  placeholderData: (previousData, previousQuery) => previousData,
  // identity function with the same behaviour as `keepPreviousData`
});
  • ์—ฌ๊ธฐ์„œ ์œ„ ๋ณ€๊ฒฝ์‚ฌํ•ญ์—๋Š” ๋ช‡ ๊ฐ€์ง€ ์ฃผ์˜์‚ฌํ•ญ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

    • placeholderData๋Š” ํ•ญ์ƒ success ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ, keepPreviousData๋Š” ์ด์ „ ์ฟผ๋ฆฌ ์ƒํƒœ๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ๊ฐ€์ ธ์˜จ ํ›„ background refetch error๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด placeholderData์˜ success ์ƒํƒœ๋Š” ์˜ค๋ฅ˜๋ผ๊ณ  ๋Š๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—๋Ÿฌ ์ž์ฒด๊ฐ€ ๊ณต์œ ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— placeholderData์˜ ๋™์ž‘์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •๋์Šต๋‹ˆ๋‹ค.

    • keepPreviousData๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ด์ „ ๋ฐ์ดํ„ฐ์˜ dateUpdatedAt ํƒ€์ž„ ์Šคํƒฌํ”„๊ฐ€ ์ œ๊ณต๋˜์—ˆ๋Š”๋ฐ, placeholderData๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด dateUpdatedAt์€ 0์œผ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

    • ๋งŒ์•ฝ, ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ํ™”๋ฉด์— ๊ณ„์† ๋ณด์—ฌ์ฃผ๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ๋™์ž‘์ด ๋ถˆ๋งŒ์กฑ์Šค๋Ÿฌ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด useEffect๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const [updatedAt, setUpdatedAt] = useState(0);

const { data, dataUpdatedAt } = useQuery({
  queryKey: ["projects", page],
  queryFn: () => fetchProjects(page),
});

useEffect(() => {
  if (dataUpdatedAt > updatedAt) {
    setUpdatedAt(dataUpdatedAt);
  }
}, [dataUpdatedAt]);

10. โญ๏ธ Window focus refetching no longer listens to the 'focus' event

  • Tanstack Query๋Š” visibilitychange ์ด๋ฒคํŠธ๋ฅผ ์ง€์›ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ €๋งŒ ์ง€์›ํ•˜๋„๋ก ๊ฒฐ์ •๋์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ์ด์ œ visibilitychange ์ด๋ฒคํŠธ๋งŒ ๋…์ ์ ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์Œ focus ๊ด€๋ จ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

11. Removed custom 'context' prop in favor of custom 'queryClient' instance

  • ์ปค์Šคํ…€ queryClient ์ธ์Šคํ„ด์Šค๋ฅผ ์œ„ํ•ด ์ปค์Šคํ…€ context prop์ด ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ธฐ์กด v4์—์„œ๋Š” context๋ฅผ ๋ชจ๋“  react query hooks์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” MicroFrontends๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ ์ ˆํ•˜๊ฒŒ ๊ฒฉ๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ, ๋‹ค๋“ค ์•Œ๋‹ค์‹ถ์ด context๋Š” ๋ฆฌ์•กํŠธ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. context๋Š” que`ryClient์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ฃผ๋Š” ์—ญํ• ์„ ํ•  ๋ฟ์ž…๋‹ˆ๋‹ค.
  • v5์—์„œ๋Š” ์œ„์™€ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ์•„๋ž˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ ์ปค์Šคํ…€ queyClient๋ฅผ ์ง์ ‘ ์ „๋‹ฌํ•จ์œผ๋กœ์จ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ๋Š” ์–ด๋–ค ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ์— ๊ตฌ์• ๋ฐ›์ง€ ์•Š๊ณ  ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
import { queryClient } from './my-client'

const { data } = useQuery(
  {
    queryKey: ['users', id],
    queryFn: () => fetch(...),
-   context: customContext
  },
+  queryClient,
)

12. โญ๏ธ Removed 'refetchPage' in favor of 'maxPages'

  • maxPages๋ฅผ ์œ„ํ•ด refetchpage๋ฅผ ์ œ๊ฑฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • v4์—์„œ๋Š” refetchPage ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ infinite queries์— ๋Œ€ํ•ด refresh ํ•  ํŽ˜์ด์ง€๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ทธ๋Ÿฌ๋‚˜ ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ refresh ํ•˜๋ฉด UI ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด ์˜ต์…˜์€ queryClient.refetchQueries์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ nomal queries๊ฐ€ ์•„๋‹Œ infinite queries์—๋Œ€ํ•ด์„œ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • v5์—์„œ๋Š” query data๋ฅผ ์ €์žฅํ•˜๊ณ , ๋‹ค์‹œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€ ์ˆ˜๋ฅผ ์ œํ•œํ•˜๋Š” infinite queries๋ฅผ ์œ„ํ•œ ์ƒˆ๋กœ์šด maxPages ์˜ต์…˜์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
useInfiniteQuery({
  queryKey: ["projects"],
  queryFn: fetchProjects,
  initialPageParam: 0,
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
  maxPages: 3,
});
  • infinite queries๋Š” ๋งŽ์€ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ฌ์ˆ˜๋ก ๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉฐ, ๋ชจ๋“  ํŽ˜์ด์ง€๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— query refetching ํ”„๋กœ์„ธ์Šค๋„ ๋Š๋ ค์ง‘๋‹ˆ๋‹ค.
  • maxPages๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํŽ˜์ด์ง€ ์ˆ˜๋ฅผ ์ œํ•œํ•˜๊ณ  ์ดํ›„์— ๋‹ค์‹œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ๋‹จ์ ์„ ๋ณด์™„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฐธ๊ณ ๋กœ infinite list๋Š” ์–‘๋ฐฉํ–ฅ์ด์—ฌ์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์œ„ ์˜ˆ์ œ์ฒ˜๋Ÿผ getNextPageParam๊ณผ getPreviousPageParam์„ ๋ชจ๋‘ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

13. โญ๏ธ infinite queries now need a 'initialPageParam'

  • ์ด์ „์—๋Š” undefined ๊ฐ’์„ ๊ฐ€์ง„ pageParam์„ queryFn์— ์ „๋‹ฌํ–ˆ๊ณ , queryFn์—์„œ pageParam์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ๊ฐ’์„ ์ •์˜ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ๊ฒฝ์šฐ ์ง๋ ฌํ™” ํ•  ์ˆ˜ ์—†๋Š” ์ฟผ๋ฆฌ ์บ์‹œ์— undefined์ธ ์ƒํƒœ๋กœ ์ €์žฅ๋œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • v5๋ถ€ํ„ฐ๋Š” ์•„๋ž˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ infinite Query ์˜ต์…˜์— ๋ช…์‹œ์ ์ธ initialPageParam์„ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
useInfiniteQuery({
   queryKey,
-  queryFn: ({ pageParam = 0 }) => fetchSomething(pageParam),
+  queryFn: ({ pageParam }) => fetchSomething(pageParam),
+  initialPageParam: 0,
   getNextPageParam: (lastPage) => lastPage.next,
})

14. Manual mode for infinite queries has been removed

  • ์ด์ „์—๋Š” ์•„๋ž˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ pageParams ๊ฐ’์„ ์ˆ˜๋™์ ์œผ๋กœ fetchNextPage ๋˜๋Š” fetchPreviousPage์— ์ง์ ‘ ์ „๋‹ฌํ•˜์—ฌ getNextPageParam ๋˜๋Š” getPreviousPageParam์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” pageParam๋ฅผ ๋ฎ์–ด์“ฐ๋Š” ๊ฒƒ์ด ํ—ˆ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
// v4
function Projects() {
  const fetchProjects = ({ pageParam = 0 }) =>
    fetch("/api/projects?cursor=" + pageParam);

  const {
    status,
    data,
    isFetching,
    isFetchingNextPage,
    fetchNextPage,
    hasNextPage,
  } = useInfiniteQuery({
    queryKey: ["projects"],
    queryFn: fetchProjects,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  });

  // Pass your own page param
  const skipToCursor50 = () => fetchNextPage({ pageParam: 50 });
}
  • ํ•˜์ง€๋งŒ ์ด pageParam์„ ๋ฎ์–ด์“ฐ๋Š” ๊ธฐ๋Šฅ์€ refetch์—์„œ๋Š” ์ „ํ˜€ ์ž‘๋™ํ•˜์ง€ ์•Š์•˜๊ณ , ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์•„๋‹ˆ์˜€์Šต๋‹ˆ๋‹ค. ์ฆ‰, infinite queries์—์„œ getNextPageParam์ด ํ•„์ˆ˜์ ์ž„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

15. โญ๏ธ Returning 'null' from 'getNextPageParam' or 'getPreviousPageParam' now indicates that there is no further page available

  • v4์—์„œ๋Š” ๋” ์ด์ƒ ํŽ˜์ด์ง€ ์—†์Œ์„ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ๋ช…์‹œ์ ์œผ๋กœ undefined๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. v5๋ถ€ํ„ฐ๋Š” undefined ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ null๊นŒ์ง€ ํฌํ•จํ•˜๋„๋ก ํ™•์žฅ๋์Šต๋‹ˆ๋‹ค.
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
  TPageParam | undefined | null;

getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) =>
  TPageParam | undefined | null;

16. No retries on the server

  • ์„œ๋ฒ„์—์„œ retry์˜ ๊ธฐ๋ณธ ๊ฐ’์€ 3์ด ์•„๋‹Œ 0์ž…๋‹ˆ๋‹ค.
  • prefetching์˜ ๊ฒฝ์šฐ ํ•ญ์ƒ ๊ธฐ๋ณธ๊ฐ’์ด 0์ด์˜€์ง€๋งŒ, suspense๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ฟผ๋ฆฌ๋Š” ์ด์ œ ์„œ๋ฒ„์—์„œ๋„ ์ง์ ‘ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—(React v18 ์ดํ›„) ์„œ๋ฒ„์—์„œ ์žฌ์‹œ๋„๋ฅผ ์ „ํ˜€ ํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

17. โญ๏ธ 'status: loading' has been changed to 'status: pending' and 'isLoading' has been changed to 'isPending' and 'isInitialLoading' has now been renamed to 'isLoading'

  • loading ์˜ต์…˜์ด pending์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์œผ๋ฉฐ, ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ isLoading ํ”Œ๋ž˜๊ทธ๊ฐ€ isPending์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
isPending: boolean;
// A derived boolean from the status variable above, provided for convenience.
isSuccess: boolean;
// A derived boolean from the status variable above, provided for convenience.
isError: boolean;
// A derived boolean from the status variable above, provided for convenience.
  • mutation์˜ ๊ฒฝ์šฐ์—๋„ isLoading ํ”Œ๋ž˜๊ทธ๊ฐ€ isPending์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
status: string;
/*
  Will be:
    - 'idle' initial status prior to the mutation function executing.
    - 'pending' if the mutation is currently executing.
    - 'error' if the last mutation attempt resulted in an error.
    - 'success' if the last mutation attempt was successful.
  'isIdle', 'isPending', 'isSuccess', 'isError': boolean variables derived from 'status'
 */
  • ๊ทธ๋ฆฌ๊ณ  isPending && isFetching์œผ๋กœ ๊ตฌํ˜„๋˜๋Š” ์ƒˆ๋กœ์šด isLoading ํ”Œ๋ž˜๊ทธ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • ์ด๋Š” ๊ธฐ์กด์˜ isInitialLoading๊ณผ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ํ•˜๋Š”๋ฐ, isInitialLoading์€ ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ ๋‹ค์Œ ๋ฉ”์ด์ € ๋ฒ„์ „ ์—…๋ฐ์ดํŠธ์—์„œ ์ œ๊ฑฐ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

18. Simplified optimistic updates

  • 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๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ๋ฐฉ์‹๋งŒ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ฐฉ๋ฒ•์€ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ๋ฅผ ํ‘œ์‹œํ•ด์•ผ ํ•˜๋Š” ์œ„์น˜๊ฐ€ ํ•œ ๊ณณ๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ์— ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค.
  • ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ์™€ ๊ด€๋ จ๋œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ optimistic-updates๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

19. Infinite Queries can prefetch multiple Pages

  • ์ด์ œ infinite queries๋„ normal queries์ฒ˜๋Ÿผ prefetch ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ์ ์œผ๋กœ query์˜ ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€๋งŒ prefetch๋˜๋ฉฐ ์ง€์ •๋œ queryKey ์•„๋ž˜์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๋‘ ๊ฐœ ์ด์ƒ์˜ ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด pages ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • prefetch์™€ ๊ด€๋ จ๋œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ prefetching๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.
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
  });
};

20. new 'combine' option for 'useQueries'

  • useQueries ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹จ์ผ ๊ฐ’์œผ๋กœ ๊ฒฐํ•ฉํ•˜๋ ค๋ฉด combine ์˜ต์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
const ids = [1,2,3]
const combinedQueries = useQueries({
  queries: ids.map(id => (
    { queryKey: ['post', id], queryFn: () => fetchPost(id) },
  )),
  combine: (results) => {
    return ({
      data: results.map(result => result.data),
      pending: results.some(result => result.isPending),
    })
  }
})
  • ์œ„ ์˜ˆ์ œ์—์„œ๋Š” combinedQueries๋Š” data์™€ pending ์†์„ฑ์ด ์žˆ๋Š” ๊ฐ์ฒด๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์˜ ๋‹ค๋ฅธ ๋ชจ๋“  ์†์„ฑ์€ ์†์‹ค๋œ๋‹ค๋Š” ์ ์„ ์ฃผ์˜ํ•ด์•ผ ๋ฉ๋‹ˆ๋‹ค.

โ€‹21. โญ๏ธ new hooks for suspense

  • v5์—์„œ๋Š” data fetching์— ๋Œ€ํ•œ suspense๊ฐ€ ๋งˆ์นจ๋‚ด ์•ˆ์ •ํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • useSuspenseQuery, useSuspenseInfiniteQuery, useSuspenseQueries 3๊ฐ€์ง€ ํ›…์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • ์œ„ 3๊ฐ€์ง€ ํ›…์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ํƒ€์ž… ๋ ˆ๋ฒจ์—์„œ data๊ฐ€ undefined ์ƒํƒœ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
import { useSuspenseQuery } from "@tanstack/react-query";

const { data } = useSuspenseQuery({ queryKey, queryFn });
  • suspense์™€ ๊ด€๋ จ๋œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ suspense๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์‹œ๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค.

22. โญ๏ธ The minimum required TypeScript version is now 4.7

  • Tanstack Query v5๋Š” ํ•„์š”ํ•œ TypeScript ์ตœ์†Œ ๋ฒ„์ „์ด v4.7์ž…๋‹ˆ๋‹ค.

23. โญ๏ธ The minimum required React version is now 18.0

  • Tanstack Query v5๋Š” ํ•„์š”ํ•œ React ์ตœ์†Œ ๋ฒ„์ „์ด v18.0์ž…๋‹ˆ๋‹ค. ์ด๋Š” React v18 ์ด์ƒ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” useSyncExternalStore ํ›…์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

24. โญ๏ธ Supported Browsers

Chrome >= 91
Firefox >= 90
Edge >= 91
Safari >= 15
iOS >= 15
opera >= 77