Giter Site home page Giter Site logo

ssi02014 / react-query-tutorial Goto Github PK

View Code? Open in Web Editor NEW
1.4K 10.0 146.0 457 KB

๐Ÿ˜ƒ TanStack Query(aka. react query) ์—์„œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๊ฐœ๋… ์ •๋ฆฌ

react-query typescript react caching tanstack-query

react-query-tutorial's Introduction

๐Ÿ’ป TanStack Query(React)

  • ํ•ด๋‹น ์ €์žฅ์†Œ๋Š” TanStack Query(React)์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๊ฐœ๋…๋“ค์„ ์ •๋ฆฌํ•œ ์ €์žฅ์†Œ์ž…๋‹ˆ๋‹ค. TanStack Query(React)์˜ ๋ชจ๋“  ํ™œ์šฉ ๋ฐฉ๋ฒ•์ด ์ž‘์„ฑ๋œ ์ƒํƒœ๋Š” ์•„๋‹ˆ๋ฉฐ, ํ•„์š”ํ•œ ๋‚ด์šฉ์€ ์ถ”๊ฐ€, ๋ณด์™„ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

Contributors

  • ๊ธฐ์—ฌํ•ด์ฃผ์‹  ๋ชจ๋“  ๋ถ„๊ป˜ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
  • ์˜คํƒˆ์ž, ๊ฐ€๋…์„ฑ์ด ์ข‹์ง€ ์•Š์€ ๋ถ€๋ถ„ ๋˜๋Š” ์ถ”๊ฐ€ ๋‚ด์šฉ์€ Pull Request, Issue ๋“ฑ์„ ์ž์œ ๋กญ๊ฒŒ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ๊ฒ€ํ†  ํ›„์— ๋ฐ˜์˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

contributors


TanStack Query(React) v5

  • โญ๏ธ TanStack Query(React) v5๊ฐ€ 23.10.17์— ๋ฆด๋ฆฌ์ฆˆ๋์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ฌธ์„œ๋Š” v5 ๊ธฐ์ค€์œผ๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • โญ๏ธ ๊ธฐ์กด v4 ๋ฌธ์„œ๋Š” react query tutorial v4 ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-10-18 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 2 09 09


์ฃผ์š” ์ปจ์…‰ ๋ฐ ๊ฐ€์ด๋“œ ๋ชฉ์ฐจ

  1. React Query ๊ฐœ์š” ๋ฐ ๊ธฐ๋Šฅ
  2. ๊ธฐ๋ณธ ์„ค์ •(QueryClientProvider, QueryClient)
  3. React Query Devtools
  4. React Query ์บ์‹ฑ ๋ผ์ดํ”„ ์‚ฌ์ดํด
  5. useQuery
  6. useQuery ์ฃผ์š” ๋ฆฌํ„ด ๋ฐ์ดํ„ฐ
  7. staleTime๊ณผ gcTime
  8. ๋งˆ์šดํŠธ ๋  ๋•Œ๋งˆ๋‹ค ์žฌ์š”์ฒญํ•˜๋Š” refetchOnMount
  9. ์œˆ๋„์šฐ๊ฐ€ ํฌ์ปค์‹ฑ ๋  ๋•Œ๋งˆ๋‹ค ์žฌ์š”์ฒญํ•˜๋Š” refetchOnWindowFocus
  10. Polling ๋ฐฉ์‹์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ refetchInterval์™€ refetchIntervalInBackground)
  11. ์ž๋™ ์‹คํ–‰์˜ enabled์™€ ์ˆ˜๋™์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋‹ค์‹œ ์š”์ฒญํ•˜๋Š” refetch
  12. ์‹คํŒจํ•œ ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์žฌ์š”์ฒญํ•˜๋Š” retry
  13. onSuccess, onError, onSettled - ๐Ÿ’ก v5 @Deprecated
  14. select๋ฅผ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜
  15. ์ฟผ๋ฆฌ๊ฐ€ pending ์ƒํƒœ์ธ ๋™์•ˆ ๋ณด์—ฌ ์ค„ ์ˆ˜ ์žˆ๋Š” placeholderData
  16. Paginated ๊ตฌํ˜„์— ์œ ์šฉํ•œ keepPreviousData - ๐Ÿ’ก v5 @Deprecated
  17. ํŠน์ • ์ฟผ๋ฆฌ ํ”„๋กœํผํ‹ฐ ๋ณ€๊ฒฝ ์‹œ์—๋งŒ ๋ฆฌ๋ Œ๋”๋ง์„ ํŠธ๋ฆฌ๊ฑฐ ํ•  ์ˆ˜ ์žˆ๋Š” notifyOnChangeProps
  18. ์ฟผ๋ฆฌ๋ฅผ ๋ณ‘๋ ฌ(Parallel) ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” useQueries
  19. ์ข…์† ์ฟผ๋ฆฌ(Dependent Queries)
  20. QueryClient ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” useQueryClient
  21. ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” initialData
  22. ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ถˆ๋Ÿฌ์˜ค๋Š” PreFetching
  23. Infinite Queries(๋ฌดํ•œ ์ฟผ๋ฆฌ) + useInfiniteQuery
  24. ์„œ๋ฒ„์™€ HTTP CUD๊ด€๋ จ ์ž‘์—…์„ ์œ„ํ•œ useMutation
  25. ์ฟผ๋ฆฌ ์ˆ˜๋™ ์ทจ์†Œ cancelQueries
  26. ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํšจํ™”ํ•  ์ˆ˜ ์žˆ๋Š” queryClient.invalidateQueries
  27. ์บ์‹œ ๋ฐ์ดํ„ฐ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ queryClient.setQueryData
  28. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)์„ ์˜ฌ๋ ค์ฃผ๋Š” Optimistic Updates(๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ)
  29. ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ Fallback UI๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•œ ErrorBoundary + useQueryErrorResetBoundary
  30. ์„œ๋ฒ„ ๋กœ๋”ฉ ์ค‘์ผ ๋•Œ Fallback UI๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•œ Suspense
  31. ์•ฑ ์ „์ฒด์— ๋™์ผํ•œ ์ฟผ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ๊ณต์œ ํ•˜๋Š” Default Query Function
  32. ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ์— ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ ์šฉ
  33. ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ESLint ์ ์šฉ
  34. ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ ์ง€์› ๋ฒ„์ „

๐Ÿ“ƒ ๊ธฐํƒ€ ์ฐธ๊ณ  ๋ฌธ์„œ

  1. QueryClient ์ฃผ์š” ๋‚ด์šฉ ์ •๋ฆฌ ๋ฌธ์„œ
  2. ๊ธฐ๋ณธ์ ์ธ React Query ์•„ํ‚คํ…์ฒ˜ ์‚ดํŽด๋ณด๊ธฐ: inside React Query

๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ์ฃผ์š” ์ฐธ๊ณ  ํŽ˜์ด์ง€


๐Ÿ“ƒ React Query ๊ฐœ์š” ๋ฐ ๊ธฐ๋Šฅ

๋ชฉ์ฐจ ์ด๋™

๊ฐœ์š”

  • react-query๋Š” ๋ฆฌ์•กํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„œ๋ฒ„ ์ƒํƒœ ๊ฐ€์ ธ์˜ค๊ธฐ, ์บ์‹ฑ, ๋™๊ธฐํ™” ๋ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ์™€ ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ๋‹ค.
  • react-query์—์„œ๋Š” ๊ธฐ์กด ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ redux, mobX๊ฐ€ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ ์ž‘์—…์— ์ ํ•ฉํ•˜์ง€๋งŒ, ๋น„๋™๊ธฐ ๋˜๋Š” ์„œ๋ฒ„ ์ƒํƒœ ์ž‘์—…์—๋Š” ๊ทธ๋‹ค์ง€ ์ข‹์ง€ ์•Š๋‹ค๊ณ  ์–ธ๊ธ‰ํ•œ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ(Client State)์™€ ์„œ๋ฒ„ ์ƒํƒœ(Server State)๋Š” ์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ฐœ๋…์ด๋ฉฐ, ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋Š” ๊ฐ๊ฐ์˜ input ๊ฐ’์œผ๋กœ ์˜ˆ๋ฅผ ๋“ค ์ˆ˜ ์žˆ๊ณ , ์„œ๋ฒ„ ์ƒํƒœ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋กœ ์˜ˆ๋ฅผ ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๊ธฐ๋Šฅ

  • ์บ์‹ฑ
  • ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ค‘๋ณต ์š”์ฒญ์„ ๋‹จ์ผ ์š”์ฒญ์œผ๋กœ ํ†ตํ•ฉ
  • ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ
  • ๋ฐ์ดํ„ฐ๊ฐ€ ์–ผ๋งˆ๋‚˜ ์˜ค๋ž˜๋˜์—ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ๊ฐ€๋Šฅํ•œ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜
  • ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋ฐ ๋ฐ์ดํ„ฐ ์ง€์—ฐ ๋กœ๋“œ์™€ ๊ฐ™์€ ์„ฑ๋Šฅ ์ตœ์ ํ™”
  • ์„œ๋ฒ„ ์ƒํƒœ์˜ ๋ฉ”๋ชจ๋ฆฌ ๋ฐ ๊ฐ€๋น„์ง€ ์ˆ˜์ง‘ ๊ด€๋ฆฌ
  • ๊ตฌ์กฐ ๊ณต์œ ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ๋ฉ”๋ชจํ™”

React Query ๊ธฐ๋ณธ ์„ค์ •

๋ชฉ์ฐจ ์ด๋™

import { QueryClient } from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: Infinity,
      // ...
    },
  },
});
  • QueryClient๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์บ์‹œ์™€ ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • QueryClient์—์„œ ๋ชจ๋“  query ๋˜๋Š” mutation์— ๊ธฐ๋ณธ ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ข…๋ฅ˜๊ฐ€ ์ƒ๋‹นํ•˜๋ฏ€๋กœ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด ๋ณด์ž.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient({ /* options */});

function App() {
  return (
   <QueryClientProvider client={queryClient}>
      <div>๋ธ”๋ผ๋ธ”๋ผ</div>
   </QueryClientProvider>;
  );
}
  • react-query๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” QueryClientProvider๋ฅผ ์ตœ์ƒ๋‹จ์—์„œ ๊ฐ์‹ธ์ฃผ๊ณ  QueryClient ์ธ์Šคํ„ด์Šค๋ฅผ client props๋กœ ๋„ฃ์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์—ฐ๊ฒฐํ•ด์•ผ ํ•œ๋‹ค.
  • ์œ„ ์˜ˆ์‹œ์—์„œ App.js์— QueryClientProvider๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๊ณ , client props์—๋‹ค queryClient๋ฅผ ์—ฐ๊ฒฐํ•จ์œผ๋กœ์จ, ์ด context๋Š” ์•ฑ์—์„œ ๋น„๋™๊ธฐ ์š”์ฒญ์„ ์•Œ์•„์„œ ์ฒ˜๋ฆฌํ•˜๋Š” background ๊ณ„์ธต์ด ๋œ๋‹ค.

Devtools

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-04-07 แ„‹แ…ฉแ„’แ…ฎ 11 53 32

๋ชฉ์ฐจ ์ด๋™

  • React Query Devtools ๊ณต์‹ ๋ฌธ์„œ
  • react-query๋Š” ์ „์šฉ devtools๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ ๋ณ„๋„์˜ ํŒจํ‚ค์ง€ ์„ค์น˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  • devtools๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด React Query์˜ ๋ชจ๋“  ๋‚ด๋ถ€ ๋™์ž‘์„ ์‹œ๊ฐํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋ฉฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋””๋ฒ„๊น… ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋‹ค.
  • devtools๋Š” ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ process.env.NODE_ENV === "development" ์ธ ๊ฒฝ์šฐ์—๋งŒ ์‹คํ–‰๋œ๋‹ค, ์ฆ‰ ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ์ž‘๋™ํ•˜๋„๋ก ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, ํ”„๋กœ์ ํŠธ ๋ฐฐํฌ ์‹œ์— Devtools ์‚ฝ์ž… ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•ด ์ค„ ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • Next 13+์˜ App Dir์—์„  dev dependency๋กœ ์„ค์น˜ํ•ด์•ผ ๋™์ž‘ํ•œ๋‹ค.
$ npm i @tanstack/react-query-devtools
# or
$ pnpm add @tanstack/react-query-devtools
# or
$ yarn add @tanstack/react-query-devtools
# or
$ bun add @tanstack/react-query-devtools
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* The rest of your application */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

options

  • initialIsOpen (Boolean)
    • true์ด๋ฉด ๊ฐœ๋ฐœ ๋„๊ตฌ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์—ด๋ ค ์žˆ๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • buttonPosition?: ("top-left" | "top-right" | "bottom-left" | "bottom-right" | "relative")
    • ๊ธฐ๋ณธ๊ฐ’: bottom-right
    • devtools ํŒจ๋„์„ ์—ฌ๋‹ซ๊ธฐ ์œ„ํ•œ ๋กœ๊ณ  ์œ„์น˜
    • relative์ผ ๋•Œ ๋ฒ„ํŠผ์€ devtools๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ์œ„์น˜์— ๋ฐฐ์น˜๋œ๋‹ค.
  • ์ผ๋ฐ˜์ ์œผ๋กœ initialIsOpen, buttonPosition์„ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋ฉฐ ๊ทธ ์™ธ์— position, client์™€ ๊ฐ™์€ ์˜ต์…˜๋“ค๋„ ์กด์žฌํ•œ๋‹ค.

์บ์‹ฑ ๋ผ์ดํ”„ ์‚ฌ์ดํด

๋ชฉ์ฐจ ์ด๋™

* Query Instances with and without cache data(์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๊ฑฐ๋‚˜ ์—†๋Š” ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค)
* Background Refetching(๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌํŒจ์นญ)
* Inactive Queries(๋น„ํ™œ์„ฑ ์ฟผ๋ฆฌ)
* Garbage Collection(๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜)
  • gcTime์˜ ๊ธฐ๋ณธ๊ฐ’ 5๋ถ„, staleTime ๊ธฐ๋ณธ๊ฐ’ 0์ดˆ๋ฅผ ๊ฐ€์ •
  1. A๋ผ๋Š” queryKey๋ฅผ ๊ฐ€์ง„ A ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค๊ฐ€ mount๋จ
  2. ๋„คํŠธ์›Œํฌ์—์„œ ๋ฐ์ดํ„ฐ fetchํ•˜๊ณ , ๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ๋Š” A๋ผ๋Š” queryKey๋กœ ์บ์‹ฑํ•จ
  3. ์ด ๋ฐ์ดํ„ฐ๋Š” fresh์ƒํƒœ์—์„œ staleTime(๊ธฐ๋ณธ๊ฐ’ 0) ์ดํ›„ stale ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ๋จ
  4. A ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค๊ฐ€ unmount๋จ
  5. ์บ์‹œ๋Š” gcTime(๊ธฐ๋ณธ๊ฐ’ 5min) ๋งŒํผ ์œ ์ง€๋˜๋‹ค๊ฐ€ ๊ฐ€๋น„์ง€ ์ฝœ๋ ‰ํ„ฐ(GC)๋กœ ์ˆ˜์ง‘๋จ
  6. ๋งŒ์ผ, gcTime ์ง€๋‚˜๊ธฐ ์ „์ด๊ณ , A ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค freshํ•œ ์ƒํƒœ๋ผ๋ฉด ์ƒˆ๋กญ๊ฒŒ mount๋˜๋ฉด ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.

useQuery

๋ชฉ์ฐจ ์ด๋™

useQuery ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

  • useQuery ๊ณต์‹ ๋ฌธ์„œ
  • useQuery๋Š” v5๋ถ€ํ„ฐ ์ธ์ž๋กœ ๋‹จ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋งŒ ๋ฐ›๋Š”๋‹ค. ๊ทธ์ค‘์— ์ฒซ ๋ฒˆ์งธ ์ธ์ž๊ฐ€ queryKey, queryFn๊ฐ€ ํ•„์ˆ˜ ๊ฐ’์ด๋‹ค.
const result = useQuery({
  queryKey, // required
  queryFn, // required
  // ...options ex) gcTime, staleTime, select, ...
});

result.data;
result.isLoading;
result.refetch;
// ...
// ์‹ค์ œ ์˜ˆ์ œ
// ๐Ÿ’ก queryFn์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ๋ฉด useQuery์˜ ํƒ€์ž… ์ถ”๋ก ์ด ์›ํ™œํ•ฉ๋‹ˆ๋‹ค.
const getAllSuperHero = async (): Promise<AxiosResponse<Hero[]>> => {
  return await axios.get("http://localhost:4000/superheroes");
};

const { data, isLoading } = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
});

1. queryKey

// (1) queryKey๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ ์œ ํ•˜๊ฒŒ ์‹๋ณ„์— ๋”ํ•ด ์ฟผ๋ฆฌ ํ•จ์ˆ˜์— ์•„๋ž˜์™€ ๊ฐ™์ด ํŽธ๋ฆฌํ•˜๊ฒŒ ์ „๋‹ฌํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
const getSuperHero = async ({
  queryKey,
}: {
  queryKey: ["super-hero", number];
}): Promise<AxiosResponse<Hero>> => {
  const heroId = queryKey[1]; // ex) queryKey: ["super-hero", "3"]

  return await axios.get(`http://localhost:4000/superheroes/${heroId}`);
};

const useSuperHeroData = (heroId: string) => {
  return useQuery({
    queryKey: ["super-hero", heroId],
    queryFn: getSuperHero, // (*)
  });
};
  • useQuery์˜ queryKey๋Š” ๋ฐฐ์—ด๋กœ ์ง€์ •ํ•ด ์ค˜์•ผ ํ•œ๋‹ค.
    • ์ด๋Š” ๋‹จ์ผ ๋ฌธ์ž์—ด๋งŒ ํฌํ•จ๋œ ๋ฐฐ์—ด์ด ๋  ์ˆ˜๋„ ์žˆ๊ณ , ์—ฌ๋Ÿฌ ๋ฌธ์ž์—ด๊ณผ ์ค‘์ฒฉ๋œ ๊ฐ์ฒด๋กœ ๊ตฌ์„ฑ๋œ ๋ณต์žกํ•œ ํ˜•ํƒœ์ผ ์ˆ˜๋„ ์žˆ๋‹ค.
// An individual todo
useQuery({ queryKey: ["todo", 5], ... })

// An individual todo in a "preview" format
useQuery({ queryKey: ["todo", 5, { preview: true }], ...})
  • useQuery๋Š” queryKey๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฟผ๋ฆฌ ์บ์‹ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ด๋‹ค.
    • ๋งŒ์•ฝ, ์ฟผ๋ฆฌ๊ฐ€ ํŠน์ • ๋ณ€์ˆ˜์— ์˜์กดํ•œ๋‹ค๋ฉด ๋ฐฐ์—ด์—๋‹ค ์ด์–ด์„œ ์ค˜์•ผ ํ•œ๋‹ค. ex: ["super-hero", heroId, ...]
    • ์ด๋Š” ์‚ฌ์‹ค ๊ต‰์žฅํžˆ ์ค‘์š”ํ•˜๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, queryClient.setQueryData ๋“ฑ๊ณผ ๊ฐ™์ด ํŠน์ • ์ฟผ๋ฆฌ์— ์ ‘๊ทผ์ด ํ•„์š” ํ•  ๋•Œ ์ดˆ๊ธฐ์— ์„ค์ •ํ•ด๋‘” ํฌ๋งท์„ ์ง€์ผœ์ค˜์•ผ ์ œ๋Œ€๋กœ ์ฟผ๋ฆฌ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์•„๋ž˜ options ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด useSuperHeroData์˜ queryKey๋Š” ["super-hero", heroId]์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด queryClient.setQueryData๋ฅผ ์ด์šฉํ•  ๋•Œ ๋˜‘๊ฐ™์ด ["super-hero", heroId] ํฌ๋งท์„ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์›ํ•˜๋Š” ์ฟผ๋ฆฌ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค.

2. queryFn

  • useQuery์˜ queryFn๋Š” Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.
// (2) ์ƒ๋‹จ์˜ queryKey ์˜ˆ์ œ์™€ ๋ฐ˜๋Œ€๋กœ queryFn ์ž์ฒด์ ์œผ๋กœ ์ธ์ž๋ฅผ ๋ฐ›๋Š” ํ˜•ํƒœ
const getSuperHero = async (heroId: string): Promise<AxiosResponse<Hero>> => {
  return await axios.get(`http://localhost:4000/superheroes/${heroId}`);
};

const useSuperHeroData = (heroId: string) => {
  return useQuery({
    queryKey: ["super-hero", heroId],
    queryFn: () => getSuperHero(heroId), // (*)
  });
};

3. options

  • useQuery ๊ณต์‹ ๋ฌธ์„œ
  • useQuery์˜ options์— ๋งŽ์ด ์“ฐ์ด๋Š” ์˜ต์…˜๋“ค์€ ์ฐจ๊ทผ์ฐจ๊ทผ ์‚ดํŽด๋ณผ ์˜ˆ์ •์ด๋‹ค. ๋ฌธ์„œ ์™ธ์— ๋”์šฑ ์ž์„ธํžˆ ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด ์œ„ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์ž.

const useSuperHeroData = (heroId: string) => {
  return useQuery({
    queryKey: ["super-hero", heroId],
    queryFn: () => getSuperHero(heroId),
    gcTime: 5 * 60 * 1000, // 5๋ถ„
    staleTime: 1 * 60 * 1000, // 1๋ถ„
    retry: 1,
    // ... options
  });
};

useQuery ์ฃผ์š” ๋ฆฌํ„ด ๋ฐ์ดํ„ฐ

const {
  data,
  error,
  status,
  fetchStatus,
  isLoading,
  isFetching,
  isError,
  refetch,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
});
  • data: ์ฟผ๋ฆฌ ํ•จ์ˆ˜๊ฐ€ ๋ฆฌํ„ดํ•œ Promise์—์„œ resolved๋œ ๋ฐ์ดํ„ฐ
  • error: ์ฟผ๋ฆฌ ํ•จ์ˆ˜์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ, ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ์˜ค๋ฅ˜ ๊ฐ์ฒด
  • status: data, ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๊ฐ’์— ๋Œ€ํ•œ ์ƒํƒœ๋ฅผ ํ‘œํ˜„ํ•˜๋Š” status๋Š” ๋ฌธ์ž์—ด ํ˜•ํƒœ๋กœ 3๊ฐ€์ง€์˜ ๊ฐ’์ด ์กด์žฌํ•œ๋‹ค.
    • pending: ์ฟผ๋ฆฌ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๊ณ , ์ฟผ๋ฆฌ ์‹œ๋„๊ฐ€ ์•„์ง ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์ƒํƒœ.
    • error: ์—๋Ÿฌ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ƒํƒœ
    • success: ์ฟผ๋ฆฌ ํ•จ์ˆ˜๊ฐ€ ์˜ค๋ฅ˜ ์—†์ด ์š”์ฒญ ์„ฑ๊ณตํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•  ์ค€๋น„๊ฐ€ ๋œ ์ƒํƒœ.
  • fetchStatus: queryFn์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋ƒ„
    • fetching: ์ฟผ๋ฆฌ๊ฐ€ ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ์ƒํƒœ
    • paused: ์ฟผ๋ฆฌ๋ฅผ ์š”์ฒญํ–ˆ์ง€๋งŒ, ์ž ์‹œ ์ค‘๋‹จ๋œ ์ƒํƒœ (network mode์™€ ์—ฐ๊ด€)
    • idle: ์ฟผ๋ฆฌ๊ฐ€ ํ˜„์žฌ ์•„๋ฌด ์ž‘์—…๋„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š” ์ƒํƒœ
  • isLoading: ์บ์‹ฑ ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๋•Œ ์ฆ‰, ์ฒ˜์Œ ์‹คํ–‰๋œ ์ฟผ๋ฆฌ์ผ ๋•Œ ๋กœ๋”ฉ ์—ฌ๋ถ€์— ๋”ฐ๋ผ true/false๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค.
    • ์ด๋Š” ์บ์‹ฑ ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ๋กœ๋”ฉ ์—ฌ๋ถ€์— ์ƒ๊ด€์—†์ด false๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    • isFetching && isPending ์™€ ๋™์ผํ•˜๋‹ค.
  • isFetching: ์บ์‹ฑ ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋”๋ผ๋„ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๋ฉด ๋กœ๋”ฉ ์—ฌ๋ถ€์— ๋”ฐ๋ผ true/false๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค.
  • isSuccess: ์ฟผ๋ฆฌ ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด true
  • isError: ์ฟผ๋ฆฌ ์š”์ฒญ ์ค‘์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ true
  • refetch: ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜๋™์œผ๋กœ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜.
  • ๊ทธ ์™ธ ๋ฐ˜ํ™˜ ๋ฐ์ดํ„ฐ๋“ค์„ ์ž์„ธํžˆ ์•Œ๊ณ  ์‹ถ์œผ๋ฉด useQuery ๊ณต์‹ ๋ฌธ์„œ ์ฐธ๊ณ 

๐Ÿ’ก status, fetchStatus ๋‚˜๋ˆ ์„œ ๋‹ค๋ฃจ๋Š” ๊ฑธ๊นŒ?

  • Why Two Different States ๊ณต์‹ ๋ฌธ์„œ
  • fetchStatus๋Š” HTTP ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ ์ƒํƒœ์™€ ์ข€ ๋” ๊ด€๋ จ๋œ ์ƒํƒœ ๋ฐ์ดํ„ฐ์ด๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, status๊ฐ€ success ์ƒํƒœ๋ผ๋ฉด ์ฃผ๋กœ fetchStatus๋Š” idle ์ƒํƒœ์ง€๋งŒ, ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ re-fetch๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ fetching ์ƒํƒœ์ผ ์ˆ˜ ์žˆ๋‹ค.
    • status๊ฐ€ ๋ณดํ†ต loading ์ƒํƒœ์ผ ๋•Œ fetchStatus๋Š” ์ฃผ๋กœ fetching๋ฅผ ๊ฐ–์ง€๋งŒ, ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์ด ๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ paused ์ƒํƒœ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
  • ์ •๋ฆฌํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
    • status๋Š” data๊ฐ€ ์žˆ๋Š”์ง€ ์—†๋Š”์ง€์— ๋Œ€ํ•œ ์ƒํƒœ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.
    • fetchStatus๋Š” ์ฟผ๋ฆฌ ์ฆ‰, queryFn ์š”์ฒญ์ด ์ง„ํ–‰ ์ค‘์ธ์ง€ ์•„๋‹Œ์ง€์— ๋Œ€ํ•œ ์ƒํƒœ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

useQuery ์ฃผ์š” ์˜ต์…˜

๋ชฉ์ฐจ ์ด๋™


staleTime๊ณผ gcTime

  • stale์€ ์šฉ์–ด ๋œป๋Œ€๋กœ ์ฉ์€์ด๋ผ๋Š” ์˜๋ฏธ์ด๋‹ค. ์ฆ‰, ์ตœ์‹  ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ๋Š” ์˜๋ฏธ์ด๋‹ค.
  • fresh๋Š” ๋œป ๊ทธ๋Œ€๋กœ ์‹ ์„ ํ•œ์ด๋ผ๋Š” ์˜๋ฏธ์ด๋‹ค. ์ฆ‰, ์ตœ์‹  ์ƒํƒœ๋ผ๋Š” ์˜๋ฏธ์ด๋‹ค.
const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  gcTime: 5 * 60 * 1000, // 5๋ถ„
  staleTime: 1 * 60 * 1000, // 1๋ถ„
});

  1. staleTime: (number | Infinity)
    • staleTime์€ ๋ฐ์ดํ„ฐ๊ฐ€ fresh์—์„œ stale ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„, ๋งŒ์•ฝ staleTime์ด 3000์ด๋ฉด fresh ์ƒํƒœ์—์„œ 3์ดˆ ๋’ค์— stale๋กœ ๋ณ€ํ™˜
    • fresh ์ƒํƒœ์ผ ๋•Œ๋Š” ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒˆ๋กญ๊ฒŒ mount ๋˜์–ด๋„ ๋„คํŠธ์›Œํฌ ์š”์ฒญ(fetch)์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค.
    • ์ฐธ๊ณ ๋กœ, staleTime์˜ ๊ธฐ๋ณธ๊ฐ’์€ 0์ด๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์œผ๋กœ fetch ํ›„์— ๋ฐ”๋กœ stale์ด ๋œ๋‹ค.
  2. gcTime: (number | Infinity)
    • ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฑฐ๋‚˜, inactive ์ƒํƒœ์ผ ๋•Œ ์บ์‹ฑ ๋œ ์ƒํƒœ๋กœ ๋‚จ์•„์žˆ๋Š” ์‹œ๊ฐ„(๋ฐ€๋ฆฌ์ดˆ)์ด๋‹ค.
    • ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค๊ฐ€ unmount ๋˜๋ฉด ๋ฐ์ดํ„ฐ๋Š” inactive ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ๋˜๋ฉฐ, ์บ์‹œ๋Š” gcTime๋งŒํผ ์œ ์ง€๋œ๋‹ค.
    • gcTime์ด ์ง€๋‚˜๋ฉด ๊ฐ€๋น„์ง€ ์ฝœ๋ ‰ํ„ฐ๋กœ ์ˆ˜์ง‘๋œ๋‹ค.
    • gcTime์ด ์ง€๋‚˜๊ธฐ ์ „์— ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋‹ค์‹œ mount ๋˜๋ฉด, ๋ฐ์ดํ„ฐ๋ฅผ fetch ํ•˜๋Š” ๋™์•ˆ ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.
    • gcTime์€ staleTime๊ณผ ๊ด€๊ณ„์—†์ด, ๋ฌด์กฐ๊ฑด inactive ๋œ ์‹œ์ ์„ ๊ธฐ์ค€์œผ๋กœ ์บ์‹œ ๋ฐ์ดํ„ฐ ์‚ญ์ œ๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
    • gcTime์˜ ๊ธฐ๋ณธ๊ฐ’์€ 5๋ถ„์ด๋‹ค. SSR ํ™˜๊ฒฝ์—์„œ๋Š” Infinity์ด๋‹ค.
  • ์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ์ ์€ staleTime๊ณผ gcTime์˜ ๊ธฐ๋ณธ๊ฐ’์€ ๊ฐ๊ฐ 0๋ถ„๊ณผ 5๋ถ„์ด๋‹ค. ๋”ฐ๋ผ์„œ staleTime์— ์–ด๋– ํ•œ ์„ค์ •๋„ ํ•˜์ง€ ์•Š์œผ๋ฉด ํ•ด๋‹น ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ(Observer)๊ฐ€ mount ๋์„ ๋•Œ ๋งค๋ฒˆ ๋‹ค์‹œ API๋ฅผ ์š”์ฒญํ•  ๊ฒƒ์ด๋‹ค.
  • staleTime์„ gcTime๋ณด๋‹ค ๊ธธ๊ฒŒ ์„ค์ •ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด, staleTime๋งŒํผ์˜ ์บ์‹ฑ์„ ๊ธฐ๋Œ€ํ–ˆ์„ ๋•Œ ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ์–ป์ง€ ๋ชปํ•  ๊ฒƒ์ด๋‹ค. ์ฆ‰, ๋‘ ๊ฐœ์˜ ์˜ต์…˜์„ ์ ์ ˆํ•˜๊ฒŒ ์„ค์ •ํ•ด ์ค˜์•ผ ํ•œ๋‹ค.
    • ์ฐธ๊ณ ๋กœ, TkDodo์˜ reply์— ๋”ฐ๋ฅด๋ฉด TkDodo๋Š” staleTime์„ gcTime๋ณด๋‹ค ์ž‘๊ฒŒ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.๋Š” ์˜๊ฒฌ์— ๋™์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•œ๋‹ค.
    • ์˜ˆ์ปจ๋Œ€, staleTime์ด 60๋ถ„์ผ์ง€๋ผ๋„ ์œ ์ €๊ฐ€ ์ž์ฃผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ผ๋ฉด ๊ตณ์ด gcTime์„ 60๋ถ„ ์ด์ƒ์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋‚ญ๋น„ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

refetchOnMount

const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  refetchOnMount: true,
});
  • refetchOnMount: boolean | "always" | ((query: Query) => boolean | "always")
  • refetchOnMount๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ stale ์ƒํƒœ์ผ ๊ฒฝ์šฐ, mount๋งˆ๋‹ค refetch๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์˜ต์…˜์ด๋‹ค.
  • refetchOnMount์˜ ๊ธฐ๋ณธ๊ฐ’์€ true์ด๋‹ค.
  • always๋กœ ์„ค์ •ํ•˜๋ฉด ๋งˆ์šดํŠธ ์‹œ๋งˆ๋‹ค ๋งค๋ฒˆ refetch๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
  • false๋กœ ์„ค์ •ํ•˜๋ฉด ์ตœ์ดˆ fetch ์ดํ›„์—๋Š” refetch ํ•˜์ง€ ์•Š๋Š”๋‹ค.

refetchOnWindowFocus

const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  refetchOnWindowFocus: true,
});
  • refetchOnWindowFocus: boolean | "always" | ((query: Query) => boolean | "always")
  • refetchOnWindowFocus๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ stale ์ƒํƒœ์ผ ๊ฒฝ์šฐ ์œˆ๋„์šฐ ํฌ์ปค์‹ฑ ๋  ๋•Œ๋งˆ๋‹ค refetch๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์˜ต์…˜์ด๋‹ค.
  • refetchOnWindowFocus์˜ ๊ธฐ๋ณธ๊ฐ’์€ true์ด๋‹ค.
  • ์˜ˆ๋ฅผ ๋“ค์–ด, ํฌ๋กฌ์—์„œ ๋‹ค๋ฅธ ํƒญ์„ ๋ˆŒ๋ €๋‹ค๊ฐ€ ๋‹ค์‹œ ์›๋ž˜ ๋ณด๋˜ ์ค‘์ธ ํƒญ์„ ๋ˆŒ๋ €์„ ๋•Œ๋„ ์ด ๊ฒฝ์šฐ์— ํ•ด๋‹นํ•œ๋‹ค. ์‹ฌ์ง€์–ด F12๋กœ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ์ฐฝ์„ ์ผœ์„œ ๋„คํŠธ์›Œํฌ ํƒญ์ด๋“ , ์ฝ˜์†” ํƒญ์ด๋“  ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ์ฐฝ์—์„œ ๋†€๋‹ค๊ฐ€ ํŽ˜์ด์ง€ ๋‚ด๋ถ€๋ฅผ ๋‹ค์‹œ ํด๋ฆญํ–ˆ์„ ๋•Œ๋„ ์ด ๊ฒฝ์šฐ์— ํ•ด๋‹นํ•œ๋‹ค.
  • always๋กœ ์„ค์ •ํ•˜๋ฉด ํ•ญ์ƒ ์œˆ๋„์šฐ ํฌ์ปค์‹ฑ ๋  ๋•Œ๋งˆ๋‹ค refetch๋ฅผ ์‹คํ–‰ํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

Polling

const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  refetchInterval: 2000,
  refetchIntervalInBackground: true,
});
Polling(ํด๋ง)์ด๋ž€?
์‹ค์‹œ๊ฐ„ ์›น์„ ์œ„ํ•œ ๊ธฐ๋ฒ•์œผ๋กœ "์ผ์ •ํ•œ ์ฃผ๊ธฐ(ํŠน์ •ํ•œ ์‹œ๊ฐ„)"๋ฅผ ๊ฐ€์ง€๊ณ  ์„œ๋ฒ„์™€ ์‘๋‹ต์„ ์ฃผ๊ณ ๋ฐ›๋Š” ๋ฐฉ์‹์ด ํด๋ง ๋ฐฉ์‹์ด๋‹ค.
react-query์—์„œ๋Š” "refetchInterval", "refetchIntervalInBackground"์„ ์ด์šฉํ•ด์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  1. refetchInterval: number | false | ((data: TData | undefined, query: Query) => number | false)
  • refetchInterval์€ ์‹œ๊ฐ„(ms)๋ฅผ ๊ฐ’์œผ๋กœ ๋„ฃ์–ด์ฃผ๋ฉด ์ผ์ • ์‹œ๊ฐ„๋งˆ๋‹ค ์ž๋™์œผ๋กœ refetch๋ฅผ ์‹œ์ผœ์ค€๋‹ค.
  1. refetchIntervalInBackground: boolean
  • refetchIntervalInBackground๋Š” refetchInterval๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ์˜ต์…˜์ด๋‹ค.
  • ํƒญ/์ฐฝ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ์žˆ๋Š” ๋™์•ˆ refetch ์‹œ์ผœ์ค€๋‹ค. ์ฆ‰, ๋ธŒ๋ผ์šฐ์ €์— focus ๋˜์–ด ์žˆ์ง€ ์•Š์•„๋„ refetch๋ฅผ ์‹œ์ผœ์ฃผ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

enabled refetch

const {
  data,
  refetch,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  enabled: false,
});

const handleClickRefetch = useCallback(() => {
  refetch();
}, [refetch]);

return (
  <div>
    {data?.data.map((hero: Data) => (
      <div key={hero.id}>{hero.name}</div>
    ))}
    <button onClick={handleClickRefetch}>Fetch Heroes</button>
  </div>
);
  • enabled: boolean
  • enabled๋Š” ์ฟผ๋ฆฌ๊ฐ€ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜์ง€ ์•Š๋„๋ก ํ•  ๋•Œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • enabled๋ฅผ false๋ฅผ ์ฃผ๋ฉด ์ฟผ๋ฆฌ๊ฐ€ ์ž๋™ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.
    • useQuery ๋ฐ˜ํ™˜ ๊ฐ’ ์ค‘ status๊ฐ€ pending ์ƒํƒœ๋กœ ์‹œ์ž‘ํ•œ๋‹ค.
  • refetch๋Š” ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜๋™์œผ๋กœ ๋‹ค์‹œ ์š”์ฒญํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ์ฟผ๋ฆฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์˜ค๋ฅ˜๋งŒ ๊ธฐ๋ก๋œ๋‹ค.
    • ์˜ค๋ฅ˜๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋ ค๋ฉด throwOnError ์†์„ฑ์„ true๋กœ ํ•ด์„œ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค.
  • ๋ณดํ†ต ์ž๋™์œผ๋กœ ์ฟผ๋ฆฌ ์š”์ฒญ์„ ํ•˜์ง€ ์•Š๊ณ  ๋ฒ„ํŠผ ํด๋ฆญ์ด๋‚˜ ํŠน์ • ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด ์š”์ฒญ์„ ์‹œ๋„ํ•  ๋•Œ ๊ฐ™์ด ์‚ฌ์šฉํ•œ๋‹ค.
  • ๐Ÿ’ก ์ฃผ์˜ํ•  ์ ์€, enabled: false๋ฅผ ์คฌ๋‹ค๋ฉด queryClient๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ• ์ค‘ invalidateQueries์™€ refetchQueries๋ฅผ ๋ฌด์‹œํ•œ๋‹ค.

retry

const {
  data,
  refetch,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  retry: 10, // ์˜ค๋ฅ˜๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์ „์— ์‹คํŒจํ•œ ์š”์ฒญ์„ 10๋ฒˆ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.
});
  • retry: (boolean | number | (failureCount: number, error: TError) => boolean)
  • retry๋Š” ์ฟผ๋ฆฌ๊ฐ€ ์‹คํŒจํ•˜๋ฉด useQuery๋ฅผ ํŠน์ • ํšŸ์ˆ˜๋งŒํผ ์žฌ์š”์ฒญํ•˜๋Š” ์˜ต์…˜์ด๋‹ค.
  • retry๊ฐ€ false์ธ ๊ฒฝ์šฐ, ์‹คํŒจํ•œ ์ฟผ๋ฆฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ค์‹œ ์‹œ๋„ํ•˜์ง€ ์•Š๋Š”๋‹ค. true์ธ ๊ฒฝ์šฐ์—๋Š” ์‹คํŒจํ•œ ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด์„œ ๋ฌดํ•œ ์žฌ์š”์ฒญ์„ ์‹œ๋„ํ•œ๋‹ค.
  • ๊ฐ’์œผ๋กœ ์ˆซ์ž๋ฅผ ๋„ฃ์„ ๊ฒฝ์šฐ, ์‹คํŒจํ•œ ์ฟผ๋ฆฌ๊ฐ€ ํ•ด๋‹น ์ˆซ์ž๋ฅผ ์ถฉ์กฑํ•  ๋•Œ๊นŒ์ง€ ์š”์ฒญ์„ ์žฌ์‹œ๋„ํ•œ๋‹ค.
  • ๊ธฐ๋ณธ๊ฐ’์€ ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” 3, ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ๋Š” 0์ด๋‹ค.

onSuccess, onError, onSettled

  • NOTE: v4๊นŒ์ง€ ์žˆ๋˜ onSuccess, onError, onSettled Callback์€ useQuery ์˜ต์…˜์—์„œ @Deprecated ๋๋‹ค. ๋‹จ, useMutation์—์„œ๋Š” ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • Breaking React Query's API on purpose ์ฐธ๊ณ 

select

const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  select: (data) => {
    const superHeroNames = data.data.map((hero) => hero.name);
    return superHeroNames;
  },
});

return (
  <div>
    {data.map((heroName, idx) => (
      <div key={`${heroName}-${idx}`}>{heroName}</div>
    ))}
  </div>
);
  • select: (data: TData) => unknown
  • select ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ ํ•จ์ˆ˜์—์„œ ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ์˜ ์ผ๋ถ€๋ฅผ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ฐธ๊ณ ๋กœ, ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ ๊ฐ’์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€๋งŒ ์ฟผ๋ฆฌ ์บ์‹œ์— ์ €์žฅ๋˜๋Š” ๋‚ด์šฉ์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค.

placeholderData

const placeholderData = useMemo(() => generateFakeHeroes(), []);

const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  placeholderData: placeholderData,
});
  • placeholderData: TData | (previousValue: TData | undefined; previousQuery: Query | undefined,) => TData
  • placeholderData๋ฅผ ์„ค์ •ํ•˜๋ฉด ์ฟผ๋ฆฌ๊ฐ€ pending ์ƒํƒœ์ธ ๋™์•ˆ ํŠน์ • ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ placeholder data๋กœ ์‚ฌ์šฉ๋œ๋‹ค.
  • placeholderData๋Š” ์บ์‹œ์— ์œ ์ง€๋˜์ง€ ์•Š์œผ๋ฉฐ, ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์™€ ๊ด€๊ณ„์—†๋Š” ๋ณด์—ฌ์ฃผ๊ธฐ์šฉ ๊ฐ€์งœ ๋ฐ์ดํ„ฐ๋‹ค.
  • placeholderData์— ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ด์ „์— ๊ด€์ฐฐ๋œ ์ฟผ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ , ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” ์ด์ „ ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋œ๋‹ค.

keepPreviousData

  • v4๊นŒ์ง€ ์žˆ๋˜ keepPreviousData์€ ํŽ˜์ด์ง€๋„ค์ด์…˜๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋˜ ์˜ต์…˜์ด์—ˆ๋‹ค. ์บ์‹ฑ ๋˜์ง€ ์•Š์€ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๋ชฉ๋ก์ด ๊นœ๋นก๊ฑฐ๋ฆฌ๋Š” ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ํ•˜์ง€๋งŒ, v5๋ถ€ํ„ฐ keepPreviousData, isPreviousData์€ ์˜ต์…˜์€ ์ œ๊ฑฐ๋๋‹ค.

  • ์ด๋“ค์€ ๊ฐ๊ฐ placeholderData์™€ isPlaceholderData ํ”Œ๋ž˜๊ทธ์™€ ๊ฑฐ์˜ ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ์•„๋ž˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ placeholderData๋ฅผ ํ™œ์šฉํ•˜๋ฉด์„œ ์ด์ „ ๋ฒ„์ „์—์„œ keepPreviousData์˜ ๊ฐ’์„ "true"๋กœ ์คฌ์„ ๋•Œ์™€ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

import { useQuery, keepPreviousData } from "@tanstack/react-query";

const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  placeholderData: keepPreviousData,
});
  • ์•„๋ž˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•ด์„œ ์œ„์˜ keepPreviousData ์˜ˆ์‹œ์™€ ๋™์ผํ•œ ๋™์ž‘์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
import { useQuery } from "@tanstack/react-query";

const {
  data,
  // ...
} = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  placeholderData: (previousData, previousQuery) => previousData,
});

notifyOnChangeProps

import { useQuery } from "@tanstack/react-query";

const { data, dataUpdatedAt } = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
  notifyOnChangeProps: ["data"], // data ๊ฐ’ ๋ณ€๊ฒฝ์‹œ์—๋งŒ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•œ๋‹ค
});
  • notifyOnChangeProps: string[] | "all" | (() => string[] | "all")
  • ์ฟผ๋ฆฌ์˜ ํŠน์ • ํ”„๋กœํผํ‹ฐ๋“ค์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ๋งŒ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ณ„๋„๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด, ์ปดํฌ๋„ŒํŠธ์—์„œ ์ ‘๊ทผํ•œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•œ๋‹ค (๊ธฐ๋ณธ ๋™์ž‘). ์ฆ‰, ์œ„ ์˜ˆ์‹œ์—์„œ notifyOnChangeProps์— ์„ค์ •๊ฐ’์„ ์ฃผ์ง€ ์•Š์•˜๋‹ค๋ฉด, data, dataUpdatedAt ์ค‘ ์–ด๋Š ํ•˜๋‚˜๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒํ•œ๋‹ค.
  • "all"๋กœ ์„ค์ •ํ•  ๊ฒฝ์šฐ, ์ฟผ๋ฆฌ์˜ ์–ด๋–ค ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค.
  • ์ฐธ๊ณ : ๊ธฐ๋ณธ ๋™์ž‘์€ Object.defineProperty()๋ฅผ ํ™œ์šฉํ•œ๋‹ค.

Parallel

๋ชฉ์ฐจ ์ด๋™

const { data: superHeroes } = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
});

const { data: friends } = useQuery({
  queryKey: ["friends"],
  queryFn: getFriends,
});
  • ๋ช‡ ๊ฐ€์ง€ ์ƒํ™ฉ์„ ์ œ์™ธํ•˜๋ฉด ์ฟผ๋ฆฌ ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ์„ ์–ธ๋œ ์ผ๋ฐ˜์ ์ธ ์ƒํ™ฉ์ผ ๋•Œ, ์ฟผ๋ฆฌ ํ•จ์ˆ˜๋“ค์€ ๊ทธ๋ƒฅ ๋ณ‘๋ ฌ๋กœ ์š”์ฒญ๋ผ์„œ ์ฒ˜๋ฆฌ๋œ๋‹ค.
  • ์ด๋Ÿฌํ•œ ํŠน์ง•์€ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ์˜ ๋™์‹œ์„ฑ์„ ๊ทน๋Œ€ํ™”์‹œํ‚จ๋‹ค.
const queryResults = useQueries({
  queries: [
    {
      queryKey: ["super-hero", 1],
      queryFn: () => getSuperHero(1),
      staleTime: Infinity, // ๋‹ค์Œ๊ณผ ๊ฐ™์ด option ์ถ”๊ฐ€ ๊ฐ€๋Šฅ!
    },
    {
      queryKey: ["super-hero", 2],
      queryFn: () => getSuperHero(2),
      staleTime: 0,
    },
    // ...
  ],
});
  • ํ•˜์ง€๋งŒ, ์ฟผ๋ฆฌ ์—ฌ๋Ÿฌ ๊ฐœ๋ฅผ ๋™์‹œ์— ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ๋ Œ๋”๋ง์ด ๊ฑฐ๋“ญ๋˜๋Š” ์‚ฌ์ด์‚ฌ์ด์— ๊ณ„์† ์ฟผ๋ฆฌ๊ฐ€ ์ˆ˜ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค๋ฉด ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋กœ์ง์ด hook ๊ทœ์น™์— ์–ด๊ธ‹๋‚  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋Ÿด ๋•Œ๋Š” useQueries๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • useQueries ํ›…์€ ๋ชจ๋“  ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๊ฐ€ ํฌํ•จ๋œ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋ฐ˜ํ™˜๋˜๋Š” ์ˆœ์„œ๋Š” ์ฟผ๋ฆฌ๊ฐ€ ์ž…๋ ฅ๋œ ์ˆœ์„œ์™€ ๋™์ผํ•˜๋‹ค.

Queries Combine

  • useQueries Combine ๊ณต์‹ ๋ฌธ์„œ

  • 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 ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ–๋Š”๋‹ค.
  • Note: ์ฐธ๊ณ ๋กœ ๊ฒฐํ•ฉํ•˜๋ฉด ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์˜ ๋‚˜๋จธ์ง€ ๋‹ค๋ฅธ ํ”„๋กœํผํ‹ฐ๋“ค์€ ์†์‹ค๋œ๋‹ค.

Dependent Queries

๋ชฉ์ฐจ ์ด๋™

  • Dependent Queries ๊ณต์‹ ๋ฌธ์„œ
  • ์ข…์† ์ฟผ๋ฆฌ๋Š” ์–ด๋–ค A๋ผ๋Š” ์ฟผ๋ฆฌ๊ฐ€ ์žˆ๋Š”๋ฐ ์ด A ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ์‚ฌ์ „์— ์™„๋ฃŒ๋˜์–ด์•ผ ํ•˜๋Š” B ์ฟผ๋ฆฌ๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด๋Ÿฌํ•œ B ์ฟผ๋ฆฌ์— ์˜์กดํ•˜๋Š” A ์ฟผ๋ฆฌ๋ฅผ ์ข…์† ์ฟผ๋ฆฌ๋ผ๊ณ  ํ•œ๋‹ค.
  • react-query์—์„œ๋Š” enabled ์˜ต์…˜์„ ํ†ตํ•ด ์ข…์† ์ฟผ๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
// ์‚ฌ์ „์— ์™„๋ฃŒ๋˜์–ด์•ผ ํ•  ์ฟผ๋ฆฌ
const { data: user } = useQuery({
  queryKey: ["user", email],
  queryFn: () => getUserByEmail(email),
});

const channelId = user?.data.channelId;

// user ์ฟผ๋ฆฌ์— ์ข…์† ์ฟผ๋ฆฌ
const { data: courses } = useQuery({
  queryKey: ["courses", channelId],
  queryFn: () => getCoursesByChannelId(channelId),
  enabled: !!channelId,
});

useQueryClient

๋ชฉ์ฐจ ์ด๋™

  • useQueryClient๋Š” QueryClient ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • QueryClient๋Š” ์บ์‹œ์™€ ์ƒํ˜ธ์ž‘์šฉํ•œ๋‹ค.
  • QueryClient๋Š” ๋‹ค์Œ ๋ฌธ์„œ์—์„œ ์ž์„ธํ•˜๊ฒŒ ๋‹ค๋ฃฌ๋‹ค.
import { useQueryClient } from "@tanstack/react-query";

const queryClient = useQueryClient();

Initial Query Data

๋ชฉ์ฐจ ์ด๋™

  • Initial Query Data ๊ณต์‹ ๋ฌธ์„œ
  • ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ์ „์— ์บ์‹œ์— ์ œ๊ณตํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.
  • initialData ์˜ต์…˜์„ ํ†ตํ•ด์„œ ์ฟผ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ์ฑ„์šฐ๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ดˆ๊ธฐ ๋กœ๋“œ ์ƒํƒœ๋„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜๋„ ์žˆ๋‹ค.
const useSuperHeroData = (heroId: string) => {
  const queryClient = useQueryClient();

  return useQuery({
    queryKey: ["super-hero", heroId],
    queryFn: () => getSuperHero(heroId),
    initialData: () => {
      const queryData = queryClient.getQueryData(["super-heroes"]) as any;
      const hero = queryData?.data?.find(
        (hero: Hero) => hero.id === parseInt(heroId)
      );

      if (hero) return { data: hero };
    },
  });
};

  • ์ฐธ๊ณ ๋กœ ์œ„ ์˜ˆ์ œ์—์„œ queryClient.getQueryData ๋ฉ”์„œ๋“œ๋Š” ๊ธฐ์กด ์ฟผ๋ฆฌ์˜ ์บ์‹ฑ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋™๊ธฐ ํ•จ์ˆ˜์ด๋‹ค. ์ฟผ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด undefined๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

Prefetching

๋ชฉ์ฐจ ์ด๋™

  • prefetching ๊ณต์‹ ๋ฌธ์„œ
  • prefetch๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๋ฏธ๋ฆฌ fetchํ•ด์˜ค๊ฒ ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.
  • ๋น„๋™๊ธฐ ์š”์ฒญ์€ ๋ฐ์ดํ„ฐ์–‘์ด ํด์ˆ˜๋ก ๋ฐ›์•„์˜ค๋Š” ์†๋„๊ฐ€ ๋Š๋ฆฌ๊ณ , ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฐ๋‹ค. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ฐ›์•„์™€์„œ ์บ์‹ฑํ•ด ๋†“์œผ๋ฉด? ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ ์ „์— ์‚ฌ์šฉ์ž๊ฐ€ ์บ์‹ฑ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด UX์— ์ข‹์€ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ๊ตฌํ˜„ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด, ํŽ˜์ด์ง€1์—์„œ ํŽ˜์ด์ง€2๋กœ ์ด๋™ํ–ˆ์„ ๋•Œ ํŽ˜์ด์ง€3์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ฐ›์•„๋†“๋Š” ๊ฒƒ์ด๋‹ค!
  • react query์—์„œ๋Š” queryClient.prefetchQuery์„ ํ†ตํ•ด์„œ prefetch ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

prefetchQuery

const prefetchNextPosts = async (nextPage: number) => {
  const queryClient = useQueryClient();
  // ํ•ด๋‹น ์ฟผ๋ฆฌ์˜ ๊ฒฐ๊ณผ๋Š” ์ผ๋ฐ˜ ์ฟผ๋ฆฌ๋“ค์ฒ˜๋Ÿผ ์บ์‹ฑ ๋œ๋‹ค.
  await queryClient.prefetchQuery({
    queryKey: ["posts", nextPage],
    queryFn: () => fetchPosts(nextPage),
    // ...options
  });
};

// ๋‹จ์ˆœ ์˜ˆ
useEffect(() => {
  const nextPage = currentPage + 1;

  if (nextPage < maxPage) {
    prefetchNextPosts(nextPage);
  }
}, [currentPage]);
  • ์ฐธ๊ณ ๋กœ prefetchQuery๋ฅผ ํ†ตํ•ด ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ด๋ฏธ ์บ์‹ฑ ๋˜์–ด ์žˆ์œผ๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š”๋‹ค.

prefetchInfiniteQuery

  • infinite ์ฟผ๋ฆฌ๋Š” ๋ฐ”๋กœ ์•„๋ž˜์— ๋‚˜์˜ค๊ฒ ์ง€๋งŒ ์ผ๋ฐ˜ ์ฟผ๋ฆฌ๋“ค์ฒ˜๋Ÿผ infinite ์ฟผ๋ฆฌ๋„ prefetch ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฟผ๋ฆฌ์˜ ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€๋งŒ prefetch ๋˜๋ฉฐ, ๊ทธ ์ด์ƒ์„ prefetch ํ•˜๋ ค๋ฉด pages ์˜ต์…˜์„ ํ™œ์šฉํ•ด์•ผ ํ•œ๋‹ค.
    • ์ด ๊ฒฝ์šฐ์—๋Š” getNextPageParam ํ•จ์ˆ˜๋ฅผ ๋ฌด์กฐ๊ฑด ์ œ๊ณตํ•ด ์ค˜์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ์ฃผ์˜ํ•˜์ž.
const prefetchTodos = async () => {
  await queryClient.prefetchInfiniteQuery({
    queryKey: ["projects"],
    queryFn: fetchProjects,
    initialPageParam: 0,
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    pages: 3, // prefetch the first 3 pages
  });
};

Infinite Queries

๋ชฉ์ฐจ ์ด๋™

  • Infinite Queries ๊ณต์‹ ๋ฌธ์„œ
  • useInfiniteQuery ๊ณต์‹ ๋ฌธ์„œ
  • Infinite Queries(๋ฌดํ•œ ์ฟผ๋ฆฌ)๋Š” ๋ฌดํ•œ ์Šคํฌ๋กค์ด๋‚˜ load more(๋” ๋ณด๊ธฐ)๊ณผ ๊ฐ™์ด ํŠน์ • ์กฐ๊ฑด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ๋ฐ›์•„์˜ค๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋ฉด ์œ ์šฉํ•˜๋‹ค.
  • react-query๋Š” ์ด๋Ÿฌํ•œ ๋ฌดํ•œ ์ฟผ๋ฆฌ๋ฅผ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด useQuery์˜ ์œ ์šฉํ•œ ๋ฒ„์ „์ธ useInfiniteQuery์„ ์ง€์›ํ•œ๋‹ค.
import { useInfiniteQuery } from "@tanstack/react-query";

// useInfiniteQuery์˜ queryFn์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” `pageParam`์ด๋ผ๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
const fetchColors = async ({
  pageParam,
}: {
  pageParam: number;
}): Promise<AxiosResponse<PaginationColors>> => {
  return await axios.get(`http://localhost:4000/colors?page=${pageParam}`);
};

const InfiniteQueries = () => {
  const { data, hasNextPage, isFetching, isFetchingNextPage, fetchNextPage } =
    useInfiniteQuery({
      queryKey: ["colors"],
      queryFn: fetchColors,
      initialPageParam: 1,
      getNextPageParam: (lastPage, allPages) => {
        return allPages.length < 4 && allPages.length + 1;
      },
      // ...
    });

  return (
    <div>
      {data?.pages.map((group, idx) => ({
        /* ... */
      }))}
      <div>
        <button disabled={!hasNextPage} onClick={() => fetchNextPage()}>
          LoadMore
        </button>
      </div>
      <div>{isFetching && !isFetchingNextPage ? "Fetching..." : null}</div>
    </div>
  );
};

์ฃผ์š” ๋ฐ˜ํ™˜

  • useInfiniteQuery๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ useQuery์™€ ์‚ฌ์šฉ๋ฒ•์€ ๋น„์Šทํ•˜์ง€๋งŒ, ์ฐจ์ด์ ์ด ์žˆ๋‹ค.
  • useInfiniteQuery๋Š” ๋ฐ˜ํ™˜ ๊ฐ’์œผ๋กœ isFetchingNextPage, isFetchingPreviousPage, fetchNextPage, fetchPreviousPage, hasNextPage ๋“ฑ์ด ์ถ”๊ฐ€์ ์œผ๋กœ ์žˆ๋‹ค.
    • data.pages: ๋ชจ๋“  ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฐ์—ด์ด๋‹ค.
    • data.pageParams: ๋ชจ๋“  ํŽ˜์ด์ง€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฐฐ์—ด์ด๋‹ค.
    • fetchNextPage: ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ fetch ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • fetchPreviousPage: ์ด์ „ ํŽ˜์ด์ง€๋ฅผ fetch ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • isFetchingNextPage: fetchNextPage ๋ฉ”์„œ๋“œ๊ฐ€ ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์•ˆ true์ด๋‹ค.
    • isFetchingPreviousPage: fetchPreviousPage ๋ฉ”์„œ๋“œ๊ฐ€ ์ด์ „ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋™์•ˆ true์ด๋‹ค.
    • hasNextPage: ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋‹ค์Œ ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ true์ด๋‹ค.
    • hasPreviousPage: ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ์ด์ „ ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ true์ด๋‹ค.

์ฃผ์š” ์˜ต์…˜

  1. initialPageParam: TPageParam
  • initialPageParam์„ ์ด์šฉํ•ด์„œ ์ฒซ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์‚ฌ์šฉํ•  ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ๋งค๊ฐœ๋ณ€์ˆ˜์ด๋‹ค. ํ•„์ˆ˜๊ฐ’์ด๋‹ค.
  1. getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) => TPageParam | undefined | null
  • getNextPageParam ์„ ์ด์šฉํ•ด์„œ ํŽ˜์ด์ง€๋ฅผ ์ฆ๊ฐ€์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ํ•„์ˆ˜๊ฐ’์ด๋‹ค.
    • getNextPageParam์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž lastPage๋Š” fetch ํ•ด์˜จ ๊ฐ€์žฅ ์ตœ๊ทผ์— ๊ฐ€์ ธ์˜จ ํŽ˜์ด์ง€ ๋ชฉ๋ก์ด๋‹ค.
    • ๋‘ ๋ฒˆ์งธ ์ธ์ž allPages๋Š” ํ˜„์žฌ๊นŒ์ง€ ๊ฐ€์ ธ์˜จ ๋ชจ๋“  ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ์ด๋‹ค.
    • ์„ธ ๋ฒˆ์งธ ์ธ์ž firstPageParam ๋Š” ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ด๋‹ค.
    • ๋„ค ๋ฒˆ์งธ ์ธ์ž allPageParams ๋Š” ๋ชจ๋“  ํŽ˜์ด์ง€์˜ ๋งค๊ฐœ๋ณ€์ˆ˜์ด๋‹ค.
  • ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋‹ค์Œ ํŽ˜์ด์ง€๊ฐ€ ์—†์Œ์„ ํ‘œ์‹œํ•˜๋ ค๋ฉด undefined ๋˜๋Š” null์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋œ๋‹ค.
  • getPreviousPageParam๋„ ์กด์žฌํ•˜๋ฉฐ, getNextPageParam์™€ ๋ฐ˜๋Œ€์˜ ์†์„ฑ์„ ๊ฐ–๊ณ  ์žˆ๋‹ค.
  1. maxPages: number | undefined
  • infinite ์ฟผ๋ฆฌ์— ์ €์žฅํ•  ์ตœ๋Œ€ ํŽ˜์ด์ง€ ์ˆ˜์ด๋‹ค.
  • ์ตœ๋Œ€ ํŽ˜์ด์ง€ ์ˆ˜์— ๋„๋‹ฌํ–ˆ๋Š”๋ฐ ์ƒˆ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ์ง€์ •๋œ ๋ฐฉํ–ฅ(next, previous)์— ๋”ฐ๋ผ ํŽ˜์ด์ง€ ๋ฐฐ์—ด์—์„œ ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€ ๋˜๋Š” ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€๊ฐ€ ์ œ๊ฑฐ๋œ๋‹ค.
  • 0 ๋˜๋Š” undefined๋ผ๋ฉด ํŽ˜์ด์ง€ ์ˆ˜๋Š” ๋ฌด์ œํ•œ์ด๋‹ค.

๐Ÿ’ก pageParam

  • queryFn์— ๋„˜๊ฒจ์ฃผ๋Š” pageParam๊ฐ€ ๋‹จ์ˆœํžˆ ๋‹ค์Œ page์˜ ๊ฐ’๋งŒ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.
  • pageParam ๊ฐ’์€ getNextPageParam์—์„œ ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝ์‹œ์ผœ ์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฌด์Šจ ๋ง์ธ์ง€ ์˜ˆ์‹œ๋ฅผ ๋ณด๋ฉด ์ดํ•ด๊ฐ€ ์‰ฝ๋‹ค. ๐Ÿ‘ ์•„๋ž˜์™€ ๊ฐ™์ด getNextPageParam์—์„œ ๋ฐ˜ํ™˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹จ์ˆœํžˆ ๋‹ค์Œ ํŽ˜์ด์ง€๊ฐ’์ด ์•„๋‹Œ ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.
const { data, hasNextPage, isFetching, isFetchingNextPage, fetchNextPage } =
  useInfiniteQuery({
    queryKey: ["colors"],
    queryFn: ({ pageParam }) => fetchColors(pageParam), // pageParam: { page: number; etc: string }
    initialPageParam: {
      page: number,
      etc: "hi",
    },
    getNextPageParam: (lastPage, allPages) => {
      return (
        allPages.length < 4 && {
          page: allPages.length + 1,
          etc: "bye",
        };
      )
    },
  });
  • ๊ทธ๋Ÿฌ๋ฉด queryFn์— ๋„ฃ์€ pageParam์—์„œ getNextPageParam์—์„œ ๋ฐ˜ํ™˜ํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
const fetchColors = async ({
  page,
  etc,
}: {
  page: number;
  etc: string;
}): Promise<AxiosResponse<PaginationColors>> => {
  return await axios.get(
    `http://localhost:4000/colors?page=${page}?etc=${etc}`
  );
};
  • ์ฆ‰, getNextPageParam์˜ ๋ฐ˜ํ™˜ ๊ฐ’์ด pageParams๋กœ ๋“ค์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— pageParams๋ฅผ ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด getNextPageParam์˜ ๋ฐ˜ํ™˜ ๊ฐ’์„ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.

useMutation

๋ชฉ์ฐจ ์ด๋™

  • useMutation ๊ณต์‹ ๋ฌธ์„œ
  • react-query์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ Get ํ•  ๋•Œ๋Š” useQuery๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • ๋งŒ์•ฝ ์„œ๋ฒ„์˜ data๋ฅผ post, patch, put, delete์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด ์ด๋•Œ๋Š” useMutation์„ ์ด์šฉํ•œ๋‹ค.
  • ์š”์•ฝํ•˜์ž๋ฉด R(read)๋Š” useQuery, CUD(Create, Update, Delete)๋Š” useMutation์„ ์‚ฌ์šฉํ•œ๋‹ค.
const mutation = useMutation({
  mutationFn: createTodo,
  onMutate() {
    /* ... */
  },
  onSuccess(data) {
    console.log(data);
  },
  onError(err) {
    console.log(err);
  },
  onSettled() {
    /* ... */
  },
});

const onCreateTodo = (e) => {
  e.preventDefault();
  mutate({ title });
};
  • useMutation์˜ ๋ฐ˜ํ™˜ ๊ฐ’์ธ mutation ๊ฐ์ฒด์˜ mutate ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์š”์ฒญ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค.
  • mutate๋Š” onSuccess, onError ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์„ฑ๊ณตํ–ˆ์„ ์‹œ, ์‹คํŒจํ–ˆ์„ ์‹œ response ๋ฐ์ดํ„ฐ๋ฅผ ํ•ธ๋“ค๋งํ•  ์ˆ˜ ์žˆ๋‹ค.
  • onMutate๋Š” mutation ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜๊ณ , mutation ํ•จ์ˆ˜๊ฐ€ ๋ฐ›์„ ๋™์ผํ•œ ๋ณ€์ˆ˜๊ฐ€ ์ „๋‹ฌ๋œ๋‹ค.
  • onSettled๋Š” try...catch...finally ๊ตฌ๋ฌธ์˜ finally์ฒ˜๋Ÿผ ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋“  ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋“  ์ƒ๊ด€์—†์ด ๋งˆ์ง€๋ง‰์— ์‹คํ–‰๋œ๋‹ค.
const mutation = useMutation(addTodo);

try {
  const todo = await mutation.mutateAsync(todo);
  console.log(todo);
} catch (error) {
  console.error(error);
} finally {
  console.log("done");
}
  • ๋งŒ์•ฝ, useMutation์„ ์‚ฌ์šฉํ•  ๋•Œ promise ํ˜•ํƒœ์˜ response๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋ผ๋ฉด mutateAsync๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์–ป์–ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ’ก mutate์™€ mutateAsync๋Š” ๋ฌด์—‡์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์„๊นŒ?

  • ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์— ์šฐ๋ฆฌ๋Š” mutate๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์œ ๋ฆฌํ•˜๋‹ค. ์™œ๋ƒํ•˜๋ฉด mutate๋Š” ์ฝœ๋ฐฑ(onSuccess, onError)๋ฅผ ํ†ตํ•ด data์™€ error์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๊ฐ€ ํŠน๋ณ„ํžˆ ํ•ธ๋“ค๋งํ•ด ์ค„ ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ํ•˜์ง€๋งŒ mutateAsync๋Š” Promise๋ฅผ ์ง์ ‘ ๋‹ค๋ฃจ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๊ฐ™์€ ๋ถ€๋ถ„์„ ์ง์ ‘ ๋‹ค๋ค„์•ผ ํ•œ๋‹ค.
    • ๋งŒ์•ฝ ์ด๋ฅผ ๋‹ค๋ฃจ์ง€ ์•Š์œผ๋ฉด unhandled promise rejection ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
  • TkDodo Blog: Mutate or MutateAsync

๐Ÿ’ก useMutation callback๊ณผ mutate callback์˜ ์ฐจ์ด

  • useMutation์€ onSuccess, onError, onSettled์™€ ๊ฐ™์€ Callback ํ•จ์ˆ˜๋“ค์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
  • ๊ทธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, mutate ์—ญ์‹œ ์œ„์™€ ๊ฐ™์€ Callback ํ•จ์ˆ˜๋“ค์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
  • ๋‘˜์˜ ๋™์ž‘์€ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์•ฝ๊ฐ„์˜ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
    • useMutation์˜ Callback ํ•จ์ˆ˜์™€ mutate์˜ Callback ํ•จ์ˆ˜๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค.
    • ์ˆœ์„œ๋Š” useMutation์˜ Callback -> mutate์˜ Callback ์ˆœ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค.
    • mutation์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „์— ์ปดํฌ๋„ŒํŠธ๊ฐ€ unmount๋œ๋‹ค๋ฉด mutate์˜ Callback์€ ์‹คํ–‰๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.
  • TkDodo๋Š” ์œ„์™€ ๊ฐ™์€ ์ด์œ ๋กœ ๋‘˜์„ ๋ถ„๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•˜๋‹ค๊ณ  ํ•œ๋‹ค.
    • ๊ผญ ํ•„์š”ํ•œ ๋กœ์ง(ex. ์ฟผ๋ฆฌ ์ดˆ๊ธฐํ™”)์€ useMutation์˜ Callback์œผ๋กœ ์‹คํ–‰์‹œํ‚จ๋‹ค.
    • ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ๋ฐ UI ๊ด€๋ จ ์ž‘์—…์€ mutate Callback์—์„œ ์‹คํ–‰์‹œํ‚จ๋‹ค.
  • TkDodo Blog: Some callbacks might not fire

cancelQueries

๋ชฉ์ฐจ ์ด๋™

  • Query Cancellation ๊ณต์‹ ๋ฌธ์„œ
  • ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ทจ์†Œํ•˜๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด ์š”์ฒญ์„ ์™„๋ฃŒํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ทจ์†Œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์š”์ฒญ์„ ์ค‘์ง€ํ•˜๋„๋ก ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋˜๋Š”, ์•„์ง HTTP ์š”์ฒญ์ด ๋๋‚˜์ง€ ์•Š์•˜์„ ๋•Œ, ํŽ˜์ด์ง€๋ฅผ ๋ฒ—์–ด๋‚  ๋•Œ๋„ ์ค‘๊ฐ„์— ์ทจ์†Œํ•ด์„œ ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ด๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด ์ฟผ๋ฆฌ๋ฅผ ์ทจ์†Œํ•˜๊ณ  ์ด์ „ ์ƒํƒœ๋กœ ๋˜๋Œ๋ฆฌ๊ธฐ ์œ„ํ•ด queryClient.cancelQueries(queryKey)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ react-query๋Š” ์ฟผ๋ฆฌ ์ทจ์†Œ๋ฟ๋งŒ์•„๋‹ˆ๋ผ queryFn์˜ Promise๋„ ์ทจ์†Œํ•œ๋‹ค.
const query = useQuery({
  queryKey: ["super-heroes"],
  queryFn: getAllSuperHero,
});

const queryClient = useQueryClient();

const onCancelQuery = (e) => {
  e.preventDefault();

  queryClient.cancelQueries({
    queryKey: ["super-heroes"],
  });
};

return <button onClick={onCancelQuery}>Cancel</button>;

์ฟผ๋ฆฌ ๋ฌดํšจํ™”

๋ชฉ์ฐจ ์ด๋™

  • invalidateQueries์€ ํ™”๋ฉด์„ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค.
  • ์˜ˆ๋ฅผ ๋“ค๋ฉด, ๊ฒŒ์‹œํŒ ๋ชฉ๋ก์—์„œ ์–ด๋–ค ๊ฒŒ์‹œ๊ธ€์„ ์ž‘์„ฑ(Post)ํ•˜๊ฑฐ๋‚˜ ๊ฒŒ์‹œ๊ธ€์„ ์ œ๊ฑฐ(Delete)ํ–ˆ์„ ๋•Œ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๋Š” ๊ฒŒ์‹œํŒ ๋ชฉ๋ก์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ตœ์‹ ํ™”ํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค.
  • ํ•˜์ง€๋งŒ ์ด๋•Œ, query Key๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ฐ•์ œ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํšจํ™”ํ•˜๊ณ  ์ตœ์‹ ํ™”๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋Ÿฐ ๊ฒฝ์šฐ์— invalidateQueries() ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ฆ‰, query๊ฐ€ ์˜ค๋ž˜๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ํŒ๋‹จํ•˜๊ณ  ๋‹ค์‹œ refetch๋ฅผ ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค!
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

const useAddSuperHeroData = () => {
  const queryClient = useQueryClient();

  return useMutation(addSuperHero, {
    onSuccess(data) {
      queryClient.invalidateQueries({ queryKey: ["super-heroes"] }); // ์ด key์— ํ•ด๋‹นํ•˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ๋ฌดํšจํ™”!
      console.log(data);
    },
    onError(err) {
      console.log(err);
    },
  });
};
  • ์ฐธ๊ณ ๋กœ, queryKey์— ["super-heroes"]์„ ๋„˜๊ฒจ์ฃผ๋ฉด queryKey์— "super-heroes"๋ฅผ ํฌํ•จํ•˜๋Š” ๋ชจ๋“  ์ฟผ๋ฆฌ๊ฐ€ ๋ฌดํšจํ™”๋œ๋‹ค.
queryClient.invalidateQueries({ queryKey: ["super-heroes"] });

// ์•„๋ž˜ query๋“ค ๋ชจ๋‘ ๋ฌดํšจํ™” ๋œ๋‹ค.
const query = useQuery({
  queryKey: ["super-heroes", "superman"],
  queryFn: fetchSuperHero,
});
const query = useQuery({
  queryKey: ["super-heroes", { id: 1 }],
  queryFn: fetchSuperHero,
});
  • ์œ„์— enabled/refetch์—์„œ๋„ ์–ธ๊ธ‰ํ–ˆ์ง€๋งŒ enabled: false ์˜ต์…˜์„ ์ฃผ๋ฉดqueryClient๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ• ์ค‘ invalidateQueries์™€ refetchQueries๋ฅผ ๋ฌด์‹œํ•œ๋‹ค.
  • ์ž์„ธํ•œ ๋‚ด์šฉ์€ queryClient.invalidateQueries ์ •๋ฆฌ๋ฅผ ์ฐธ๊ณ ํ•˜์ž.

์บ์‹œ ๋ฐ์ดํ„ฐ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ

๋ชฉ์ฐจ ์ด๋™

  • queryClient.setQueryData ๊ณต์‹ ๋ฌธ์„œ
  • ๋ฐ”๋กœ ์œ„์—์„œ queryClient.invalidateQueries๋ฅผ ์ด์šฉํ•ด ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ตœ์‹ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ดค๋Š”๋ฐ queryClient.setQueryData๋ฅผ ์ด์šฉํ•ด์„œ๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • queryClient.setQueryData๋Š” ์ฟผ๋ฆฌ์˜ ์บ์‹œ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋™๊ธฐ ํ•จ์ˆ˜์ด๋‹ค.
  • setQueryData์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” updater ํ•จ์ˆ˜์ด๋‹ค. ํ•ด๋‹น ํ•จ์ˆ˜์˜ ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” oldData๋กœ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
const useAddSuperHeroData = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: addSuperHero,
    onSuccess(data) {
      queryClient.setQueryData(["super-heroes"], (oldData: any) => {
        return {
          ...oldData,
          data: [...oldData.data, data.data],
        };
      });
    },
    onError(err) {
      console.log(err);
    },
  });
};

Optimistic Update

๋ชฉ์ฐจ ์ด๋™

  • Optimistic Update(๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ)๋ž€ ์„œ๋ฒ„ ์—…๋ฐ์ดํŠธ ์‹œ UI์—์„œ๋„ ์–ด์ฐจํ”ผ ์—…๋ฐ์ดํŠธํ•  ๊ฒƒ์ด๋ผ๊ณ (๋‚™๊ด€์ ์ธ) ๊ฐ€์ •ํ•ด์„œ ๋ฏธ๋ฆฌ UI๋ฅผ ์—…๋ฐ์ดํŠธ ์‹œ์ผœ์ฃผ๊ณ  ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ๊ฒ€์ฆ๋ฐ›๊ณ  ์—…๋ฐ์ดํŠธ ๋˜๋Š” ๋กค๋ฐฑํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
  • ์˜ˆ๋ฅผ ๋“ค์–ด facebook์— ์ข‹์•„์š” ๋ฒ„ํŠผ์ด ์žˆ๋Š”๋ฐ ์ด๊ฒƒ์„ ์œ ์ €๊ฐ€ ๋ˆ„๋ฅธ๋‹ค๋ฉด, ์ผ๋‹จ client ์ชฝ state๋ฅผ ๋จผ์ € ์—…๋ฐ์ดํŠธํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งŒ์•ฝ์— ์‹คํŒจํ•œ๋‹ค๋ฉด, ์˜ˆ์ „ state๋กœ ๋Œ์•„๊ฐ€๊ณ  ์„ฑ๊ณตํ•˜๋ฉด ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ fetch ํ•ด์„œ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์™€ ํ™•์‹คํžˆ ์—ฐ๋™์„ ์ง„ํ–‰ํ•œ๋‹ค.
  • Optimistic Update๊ฐ€ ์ •๋ง ์œ ์šฉํ•  ๋•Œ๋Š” ์ธํ„ฐ๋„ท ์†๋„๊ฐ€ ๋Š๋ฆฌ๊ฑฐ๋‚˜ ์„œ๋ฒ„๊ฐ€ ๋Š๋ฆด ๋•Œ์ด๋‹ค. ์œ ์ €๊ฐ€ ํ–‰ํ•œ ์•ก์…˜์„ ๊ธฐ๋‹ค๋ฆด ํ•„์š” ์—†์ด ๋ฐ”๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX) ์ธก๋ฉด์—์„œ ์ข‹๋‹ค.
const useAddSuperHeroData = () => {
  const queryClient = useQueryClient();
  return useMutation({
    mutateFn: addSuperHero,
    onMutate: async (newHero: any) => {
      await queryClient.cancelQueries(["super-heroes"]);

      // ์ด์ „ ๊ฐ’
      const previousHeroData = queryClient.getQueryData(["super-heroes"]);

      // ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ ์ง„ํ–‰
      queryClient.setQueryData(["super-heroes"], (oldData: any) => {
        return {
          ...oldData,
          data: [
            ...oldData.data,
            { ...newHero, id: oldData?.data?.length + 1 },
          ],
        };
      });

      // ๊ฐ’์ด ๋“ค์–ด์žˆ๋Š” context ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
      return { previousHeroData };
    },
    // mutation์ด ์‹คํŒจํ•˜๋ฉด onMutate์—์„œ ๋ฐ˜ํ™˜๋œ context๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กค๋ฐฑ ์ง„ํ–‰
    onError(error, hero, context: any) {
      queryClient.setQueryData(["super-heroes"], context.previousHeroData);
    },
    // ์˜ค๋ฅ˜ ๋˜๋Š” ์„ฑ๊ณต ํ›„์—๋Š” ํ•ญ์ƒ refetch
    onSettled() {
      queryClient.invalidateQueries(["super-heroes"]);
    },
  });
};
  • ์ฐธ๊ณ ๋กœ ์œ„ ์˜ˆ์ œ์—์„œ cancelQueries๋Š” ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ทจ์†Œ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ์ทจ์†Œ์‹œํ‚ฌ query์˜ queryKey๋ฅผ cancelQueries์˜ ์ธ์ž๋กœ ๋ณด๋‚ด ์‹คํ–‰์‹œํ‚จ๋‹ค.
  • ์˜ˆ๋ฅผ ๋“ค์–ด, ์š”์ฒญ์„ ์™„๋ฃŒํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์ทจ์†Œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์š”์ฒญ์„ ์ค‘์ง€ํ•˜๋Š” ๊ฒฝ์šฐ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

useQueryErrorResetBoundary

๋ชฉ์ฐจ ์ด๋™

  • useQueryErrorResetBoundary ๊ณต์‹ ๋ฌธ์„œ
  • react-query์—์„œ ErrorBoundary์™€ useQueryErrorResetBoundary๋ฅผ ๊ฒฐํ•ฉํ•ด ์„ ์–ธ์ ์œผ๋กœ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ Fallback UI๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • ErrorBoundary์— ๋Œ€ํ•œ ์„ค๋ช…์€ ํ•ด๋‹น ๋ฌธ์„œ ์ฐธ๊ณ  ErrorBoundary

  • useQueryErrorResetBoundary๋Š” ErrorBoundary์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๋Š”๋ฐ ์ด๋Š”, ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฆฌ์•กํŠธ ๊ณต์‹ ๋ฌธ์„œ์—์„œ ๊ธฐ๋ณธ ์ฝ”๋“œ ๋ฒ ์ด์Šค๊ฐ€ ์ œ๊ณต๋˜๊ธด ํ•˜์ง€๋งŒ ์ข€ ๋” ์‰ฝ๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” react-error-boundary ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜๊ณ , react-query ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋„ ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์‚ฌ์šฉ์„ ์˜ˆ์‹œ๋กœ ์ œ๊ณตํ•ด ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— react-error-boundary๋ฅผ ์„ค์น˜ํ•ด์„œ ์‚ฌ์šฉํ•ด ๋ณด์ž.
$ npm i react-error-boundary
# or
$ pnpm add react-error-boundary
# or
$ yarn add react-error-boundary
# or
$ bun add react-error-boundary
  • ์„ค์น˜ ํ›„์— ์•„๋ž˜์™€ ๊ฐ™์€ QueryErrorBoundary๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๊ทธ ๋‚ด๋ถ€์— useQueryErrorResetBoundary ํ›…์„ ํ˜ธ์ถœํ•ด reset ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  • ์•„๋ž˜ ์ฝ”๋“œ ๋‚ด์šฉ์€ ๋‹จ์ˆœํ•˜๋‹ค.
    • Error๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ErrorBoundary์˜ fallbackRender prop์œผ๋กœ ๋„˜๊ธด ๋‚ด์šฉ์ด ๋ Œ๋”๋ง ๋˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด children ๋‚ด์šฉ์ด ๋ Œ๋”๋ง ๋œ๋‹ค.
    • ๋˜ํ•œ, fallbackRender์— ๋„ฃ์–ด์ฃผ๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ๋งค๊ฐœ ๋ณ€์ˆ˜๋กœ resetErrorBoundary๋ฅผ ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น์„ ํ†ตํ•ด ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋ฅผ ํ†ตํ•ด ๋ชจ๋“  ์ฟผ๋ฆฌ ์—๋Ÿฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” button์„ ํด๋ฆญํ•˜๋ฉด ์—๋Ÿฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ฒŒ๋” ์ž‘์„ฑํ–ˆ๋‹ค.
import { useQueryErrorResetBoundary } from "@tanstack/react-query"; // (*)
import { ErrorBoundary } from "react-error-boundary"; // (*)

interface Props {
  children: React.ReactNode;
}

const QueryErrorBoundary = ({ children }: Props) => {
  const { reset } = useQueryErrorResetBoundary(); // (*)

  return (
    <ErrorBoundary
      onReset={reset}
      fallbackRender={({ resetErrorBoundary }) => (
        <div>
          Error!!
          <button onClick={() => resetErrorBoundary()}>Try again</button>
        </div>
      )}
    >
      {children}
    </ErrorBoundary>
  );
};

export default QueryErrorBoundary;
  • ๊ทธ๋ฆฌ๊ณ  App.js์—๋‹ค QueryErrorBoundary ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค. ์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ์ ์€ queryClient ์˜ต์…˜์—๋‹ค { throwOnError: true }๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ด๋‹ค. ๊ทธ๋ž˜์•ผ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ErrorBoundary ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import QueryErrorBoundary from "./components/ErrorBoundary"; // (*)

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      throwOnError: true, // (*) ์—ฌ๊ธฐ์„œ๋Š” ๊ธ€๋กœ๋ฒŒ๋กœ ์„ธํŒ…ํ–ˆ์ง€๋งŒ, ๊ฐœ๋ณ„ ์ฟผ๋ฆฌ๋กœ ์„ธํŒ… ๊ฐ€๋Šฅ
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <QueryErrorBoundary>{/* ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค */}</QueryErrorBoundary>
    </QueryClientProvider>
  );
}

Suspense

๋ชฉ์ฐจ ์ด๋™

  • ErrorBoundary๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋ณด์—ฌ์ฃผ๋Š” Fallback UI๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๊ณ , ๋ฆฌ์•กํŠธ ์ฟผ๋ฆฌ๋Š” Suspense์™€๋„ ๊ฒฐํ•ฉํ•ด์„œ ์„œ๋ฒ„ ํ†ต์‹  ์ƒํƒœ๊ฐ€ ๋กœ๋”ฉ ์ค‘์ผ ๋•Œ Fallback UI๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๊ฒŒ ์„ ์–ธ์ ์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ฐธ๊ณ ๋กœ, Suspense ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ์•กํŠธ v16๋ถ€ํ„ฐ ์ œ๊ณต๋˜๋Š” Component Lazy Loading์ด๋‚˜ Data Fetching ๋“ฑ์˜ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ๋•Œ, ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ Fallback UI(ex: Loader)๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋‹ค.
import { Suspense } from "react";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // suspense: true, - ๐Ÿ’ก v5๋ถ€ํ„ฐ Deprecated
      // useQuery/useInfiniteQuery์™€ ๊ฐ™์€ ์ผ๋ฐ˜ ํ›… ๋Œ€์‹  useSuspenseQuery/useSuspenseInfiniteQuery์™€ ๊ฐ™์€ suspense ํ›… ์‚ฌ์šฉ
      throwOnError: true,
    },
  },
});

function App() {
  return (
    <QueryErrorBoundary>
      <Suspense fallback={<Loader />}>{/* ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค */}</Suspense>
    </QueryErrorBoundary>;
  );
}
  • ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์šฐ๋ฆฌ๋Š” ์„œ๋ฒ„ ์ƒํƒœ๊ฐ€ ๋กœ๋”ฉ์ผ ๋•Œ Loader ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒ ๋‹ค!๋ผ๊ณ  ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Suspense์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์—์„œ ์–ด๋–ค ๋กœ์ง์ด ๋™์ž‘ํ•˜๋Š”์ง€ ์šฐ๋ฆฌ๋Š” ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„๋„๋œ๋‹ค. ์ด์ฒ˜๋Ÿผ ๋‚ด๋ถ€ ๋ณต์žก์„ฑ์„ ์ถ”์ƒํ™”ํ•˜๋Š” ๊ฒŒ ๋ฐ”๋กœ ์„ ์–ธํ˜• ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.
  • ์œ„์™€ ๊ฐ™์ด react-query(useSuspenseQuery)์™€ ๊ฒฐํ•ฉํ•œ Suspense๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๊ณผ์ •์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.
1. Suspense mount
2. MainComponent mount
3. MainComponent์—์„œ useSuspenseQuery ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์š”์ฒญ
4. MainComponent unmount, fallback UI์ธ Loader mount
5. ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ์š”์ฒญ์ด ์™„๋ฃŒ๋˜๋ฉด fallback UI์ธ Loader unmount
6. MainComponent mount

๐Ÿ’ก New hooks for suspense

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

const fetchGroups = async (): Promise<{ data: Group[] }> => {
  const res = await axios.get("/groups");
  return res;
};

// as-is
const { data } = useQuery({
  queryKey: ["groups"],
  queryFn: fetchGroups,
  select: (data) => data.data,
});

// to-be
const { data } = useSuspenseQuery({
  queryKey: ["groups"],
  queryFn: fetchGroups,
  select: (data) => data.data,
});

๐Ÿ’ก @suspensive/react-query

  • TanStack Query(React) ๊ณต์‹ ๋ฌธ์„œ์˜ Community Resources์—์„œ๋Š” Suspense๋ฅผ ๋” ํƒ€์ž… ์„ธ์ดํ”„ํ•˜๊ฒŒ ์ž˜ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด useSuspenseQuery, useSuspenseQueries, useSuspenseInfiniteQuery๋ฅผ ์ œ๊ณตํ•˜๋Š” @suspensive/react-query๋ฅผ ์†Œ๊ฐœํ•˜๊ณ  ์žˆ๋‹ค.

  • suspensive/react-query์˜ ํ›…(useSuspenseQuery, useSuspenseQueries, useSuspenseInfiniteQuery)์€ @tanstack/react-query v5 ๋ฒ„์ „์— ์ถ”๊ฐ€(๊ด€๋ จ Pull Request)๋˜๊ณ  ๊ณต์‹ API๋กœ ์ด ํŽ˜์ด์ง€์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


Default Query Function

๋ชฉ์ฐจ ์ด๋™

  • Default Query Function ๊ณต์‹ ๋ฌธ์„œ
  • ์•ฑ ์ „์ฒด์—์„œ ๋™์ผํ•œ ์ฟผ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ๊ณต์œ ํ•˜๊ณ , queryKey๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ€์ ธ์™€์•ผ ํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์‹๋ณ„ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด QueryClient์— queryFn ์˜ต์…˜์„ ํ†ตํ•ด Default Query Function์„ ์ง€์ •ํ•ด ์ค„ ์ˆ˜ ์žˆ๋‹ค.
// ๊ธฐ๋ณธ ์ฟผ๋ฆฌ ํ•จ์ˆ˜
const getSuperHero = async ({ queryKey }: any) => {
  const heroId = queryKey[1];

  return await axios.get(`http://localhost:4000/superheroes/${heroId}`);
};

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      queryFn: getSuperHero,
      // ...queries options
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>{/* ... */}</QueryClientProvider>
  );
}
  • QueryClient์— ์•ฑ ์ „์ฒด์—์„œ ์‚ฌ์šฉํ•  ์ฟผ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•ด ์ค€๋‹ค.
// ์‚ฌ์šฉ ์˜ˆ์‹œ
const useSuperHeroData = (heroId: string) => {
  return useQuery({ queryKey: ["super-hero", heroId] });
};
// ๋‹ค์Œ ํ˜•ํƒœ๋Š” ๋ถˆ๊ฐ€๋Šฅ
const useSuperHeroData = (heroId: string) => {
  return useQuery({
    queryKey: ["super-hero", heroId],
    queryFn: () => getSuperHero(heroId),
  });
};
  • useQuery์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ queryKey๋งŒ ๋„ฃ์–ด์ฃผ๋ฉด ๋‘ ๋ฒˆ์งธ ์ธ์ž์— ๋“ค์–ด๊ฐˆ queryFn์€ ์ž๋™์œผ๋กœ ์„ค์ •๋œ ๊ธฐ๋ณธ ์ฟผ๋ฆฌ ํ•จ์ˆ˜๊ฐ€ ๋“ค์–ด๊ฐ„๋‹ค.
  • ์ผ๋ฐ˜์ ์œผ๋กœ useQuery๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์™€ ๋‹ฌ๋ฆฌ queryFn์„ ์ง€์ •ํ•˜์ง€ ์•Š๊ธฐ์— ์ฟผ๋ฆฌ ํ•จ์ˆ˜์— ์ง์ ‘ ์ธ์ž๋ฅผ ๋„ฃ์–ด์ฃผ๋Š” ํ˜•ํƒœ์˜ ์‚ฌ์šฉ์€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

React Query Typescript

๋ชฉ์ฐจ ์ด๋™

  • React Query๋Š” TypeScript์˜ ์ œ๋„ค๋ฆญ(Generics)์„ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๊ณ  API๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

useQuery

ํ˜„์žฌ useQuery๊ฐ€ ๊ฐ–๊ณ  ์žˆ๋Š” ์ œ๋„ค๋ฆญ์€ 4๊ฐœ์ด๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. TQueryFnData: useQuery๋กœ ์‹คํ–‰ํ•˜๋Š” query function์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ์˜ ํƒ€์ž…์„ ์ง€์ •ํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
  2. TError: query function์˜ error ํ˜•์‹์„ ์ •ํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
  3. TData: useQuery์˜ data์— ๋‹ด๊ธฐ๋Š” ์‹ค์งˆ์ ์ธ ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ ๋งํ•œ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ œ๋„ค๋ฆญ๊ณผ์˜ ์ฐจ์ด์ ์€ select์™€ ๊ฐ™์ด query function์˜ ๋ฐ˜ํ™˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ ํ•ธ๋“ค๋ง์„ ํ†ตํ•ด ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์ข‹๋‹ค.
  4. TQueryKey: useQuery์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž queryKey์˜ ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•ด ์ฃผ๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
// useQuery์˜ ํƒ€์ž…
export function useQuery<
  TQueryFnData = unknown,
  TError = DefaultError,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey
>
import { AxiosError } from "axios";

// useQuery ํƒ€์ž… ์ ์šฉ ์˜ˆ์‹œ
const { data } = useQuery<
  AxiosResponse<Hero[]>,
  AxiosError,
  string[],
  ["super-heroes", number]
>({
  queryKey: ["super-heroes", id],
  queryFn: getSuperHero,
  select: (data) => {
    const superHeroNames = data.data.map((hero) => hero.name);
    return superHeroNames;
  },
});

/**
 ์ฃผ์š” ํƒ€์ž…
 * data: string[] | undefined
 * error: AxiosError<any, any>
 * select: (data: AxiosResponse<Hero[]>): string[]
 */

useMutation

useMutation๋„ useQuery์™€ ๋™์ผํ•˜๊ฒŒ ํ˜„์žฌ 4๊ฐœ์ด๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. TData: useMutation์— ๋„˜๊ฒจ์ค€ mutation function์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ์˜ ํƒ€์ž…์„ ์ง€์ •ํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
    • data์˜ ํƒ€์ž…๊ณผ onSuccess(1๋ฒˆ์งธ ์ธ์ž) ์ธ์ž์˜ ํƒ€์ž…์œผ๋กœ ํ™œ์šฉ๋œ๋‹ค.
  2. TError: useMutation์— ๋„˜๊ฒจ์ค€ mutation function์˜ error ํ˜•์‹์„ ์ •ํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
  3. TVariables: mutate ํ•จ์ˆ˜์— ์ „๋‹ฌ ํ•  ์ธ์ž๋ฅผ ์ง€์ •ํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
    • onSuccess(2๋ฒˆ์งธ ์ธ์ž), onError(2๋ฒˆ์งธ ์ธ์ž), onMutate(1๋ฒˆ์งธ ์ธ์ž), onSettled(3๋ฒˆ์งธ ์ธ์ž) ์ธ์ž์˜ ํƒ€์ž…์œผ๋กœ ํ™œ์šฉ๋œ๋‹ค.
  4. TContext: mutation function์„ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ์ˆ˜ํ–‰ํ•˜๋Š” onMutate ํ•จ์ˆ˜์˜ return๊ฐ’์„ ์ง€์ •ํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
    • onMutate์˜ ๊ฒฐ๊ณผ๊ฐ’์˜ ํƒ€์ž…์„ onSuccess(3๋ฒˆ์งธ ์ธ์ž), onError(3๋ฒˆ์งธ ์ธ์ž), onSettled(4๋ฒˆ์งธ ์ธ์ž)์—์„œ ํ™œ์šฉํ•˜๋ ค๋ฉด ํ•ด๋‹น ํƒ€์ž…์„ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค.
export function useMutation<
  TData = unknown,
  TError = DefaultError,
  TVariables = void,
  TContext = unknown
>
// useMutation ํƒ€์ž… ์ ์šฉ ์˜ˆ์‹œ
const { mutate } = useMutation<Todo, AxiosError, number, number>({
  mutationFn: postTodo,
  onSuccess: (res, id, nextId) => {},
  onError: (err, id, nextId) => {},
  onMutate: (id) => id + 1,
  onSettled: (res, err, id, nextId) => {},
});

const onClick = () => {
  mutate(5);
};

/** 
 ์ฃผ์š” ํƒ€์ž…
 * data: Todo
 * error: AxiosError<any, any>
 * onSuccess: (res: Todo, id: number, nextId: number)
 * onError: (err: AxiosError, id: number, nextId: number)
 * onMutate: (id: number)
 * onSettled: (res: Todo, err: AxiosError, id: number, nextId: number),
*/

useInfiniteQuery

ํ˜„์žฌ useInfiniteQuery ๊ฐ–๊ณ  ์žˆ๋Š” ์ œ๋„ค๋ฆญ์€ 4๊ฐœ์ด๋ฉฐ, useQuery์™€ ์œ ์‚ฌํ•˜๋‹ค.

  1. TQueryFnData: useInfiniteQuery๋กœ ์‹คํ–‰ํ•˜๋Š” query function์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ์˜ ํƒ€์ž…์„ ์ง€์ •ํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
  2. TError: query function์˜ error ํ˜•์‹์„ ์ •ํ•˜๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
  3. TData: useInfiniteQuery์˜ data์— ๋‹ด๊ธฐ๋Š” ์‹ค์งˆ์ ์ธ ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ ๋งํ•œ๋‹ค. ์ฒซ ๋ฒˆ์งธ ์ œ๋„ค๋ฆญ๊ณผ์˜ ์ฐจ์ด์ ์€ select์™€ ๊ฐ™์ด query function์˜ ๋ฐ˜ํ™˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ ํ•ธ๋“ค๋ง์„ ํ†ตํ•ด ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์ข‹๋‹ค.
  4. TQueryKey: useInfiniteQuery์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž queryKey์˜ ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•ด ์ฃผ๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋‹ค.
export function useInfiniteQuery<
  TQueryFnData = unknown,
  TError = DefaultError,
  TData = InfiniteData<TQueryFnData>,
  TQueryKey extends QueryKey = QueryKey
>
const {
  data,
  hasNextPage,
  fetchNextPage,
  //...
} = useInfiniteQuery<
  AxiosResponse<PaginationColors>,
  AxiosError,
  InfiniteData<AxiosResponse<PaginationColors>, number>,
  ["colors"]
>({
  queryKey: ["colors"],
  queryFn: fetchColors,
  initialPageParam: 1,
  getNextPageParam: (lastPage, allPages) => {
    return allPages.length < 4 && allPages.length + 1;
  },
  // ...options
});

/**
 ์ฃผ์š” ํƒ€์ž…
 * data: InfiniteData<AxiosResponse<PaginationColors>, number> | undefined
 * error: AxiosError<any, any>
 * select: (data: InfiniteData<AxiosResponse<PaginationColors>, number>): InfiniteData<AxiosResponse<PaginationColors>, number>
 * getNextPageParam: GetNextPageParamFunction<number, AxiosResponse<PaginationColors>>
*/

๐Ÿ’ก Typescript Best Practice

  • TypeScript ๊ณต์‹ ๋ฌธ์„œ
  • ์œ„์˜ ์ œ๋„ค๋ฆญ์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฑด ์ฝ”๋“œ์˜ ๋ณต์žก๋„๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค. ํ•˜์ง€๋งŒ react query๋Š” ํƒ€์ž…์„ ์ž˜ ์ „๋‹ฌํ•˜๋ฏ€๋กœ ๊ตณ์ด ์ œ๋„ค๋ฆญ์„ ๋ชจ๋‘ ์ง์ ‘ ์ œ๊ณตํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ queryFn์˜ ํƒ€์ž…์„ ์ž˜ ์ •์˜ํ•ด์„œ ํƒ€์ž… ์ถ”๋ก ์ด ์›ํ™œํ•˜๊ฒŒ ๋˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
const fetchGroups = async (): Promise<AxiosResponse<Group[]> => {
  return await axios.get("/groups");
};

const { data } = useQuery({
  queryKey: ["groups"],
  queryFn: fetchGroups,
  select: (data) => data.data,
});

/**
 ์ฃผ์š” ํƒ€์ž…
 * data: AxiosResponse<Group[]> | undefined
 * error: Error | null
 * select: (data: AxiosResponse<Group[]>): Group[]
 */

React Query ESLint Plugin

๋ชฉ์ฐจ ์ด๋™

  • Tanstack Query๋Š” ์ž์ฒด ESLint Plugin์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ํ†ตํ•ด ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ์ ์šฉํ•˜๊ณ , ์ผ๋ฐ˜์ ์ธ ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ค์น˜

$ npm i -D @tanstack/eslint-plugin-query
# or
$ pnpm add -D @tanstack/eslint-plugin-query
# or
$ yarn add -D @tanstack/eslint-plugin-query
# or
$ bun add -D @tanstack/eslint-plugin-query

์‚ฌ์šฉ ๋ฐฉ๋ฒ•(1)

  • ํ”Œ๋Ÿฌ๊ทธ์ธ์— ๋Œ€ํ•œ ๊ถŒ์žฅํ•˜๋Š” ๋ชจ๋“  rule์„ ์ ์šฉํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด .eslintrc.js ํŒŒ์ผ์˜ extends๋ฐฐ์—ด ์•ˆ์— plugin:@tanstack/eslint-plugin-query/recommended์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
module.exports = {
  // ...
  extends: ["plugin:@tanstack/eslint-plugin-query/recommended"],
  rules: {
    /* ์ˆ˜์ •ํ•˜๊ณ ์ž ํ•˜๋Š” rule ์ถ”๊ฐ€ ๊ฐ€๋Šฅ... */
  },
};
  • ๋ฌผ๋ก , rule์„ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด rules์— ์•„๋ž˜ ์‚ฌ์šฉ๋ฐฉ๋ฒ•(2)์™€ ๊ฐ™์ด rule์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ ๋ฐฉ๋ฒ•(2)

  • ์›ํ•˜๋Š” rule์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์„ค์ •ํ•ด์„œ ์ ์šฉํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด .eslintrc.js ํŒŒ์ผ์˜ plugins๋ฐฐ์—ด ์•ˆ์— @tanstack/query๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ์ ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” rules์— ๊ทœ์น™์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
module.exports = {
  // ...
  plugins: ["@tanstack/query"],
  rules: {
    "@tanstack/query/exhaustive-deps": "error",
    "@tanstack/query/no-rest-destructuring": "warn",
    "@tanstack/query/stable-query-client": "error",
  },
};

์ง€์› ๋ฒ„์ „

๋ชฉ์ฐจ ์ด๋™

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

  • Tanstack Query v5์— ํ•„์š”ํ•œ React ์ตœ์†Œ ๋ฒ„์ „์€ v18์ž…๋‹ˆ๋‹ค.

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

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

react-query-tutorial's People

Contributors

100gyeon avatar cdm1263 avatar chogyejin avatar gaic4o avatar jthw1005 avatar k-kbk avatar manudeli avatar moon9ua avatar okinawaa avatar sanginchun avatar seiwonpark avatar seunghyune avatar ssi02014 avatar stakbucks avatar westhyun avatar yunwoo-yu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-query-tutorial's Issues

์ฟผ๋ฆฌ ๋ฌดํšจํ™” invalidateQueries ์„ค๋ช… ๊ด€๋ จ

์•ˆ๋…•ํ•˜์„ธ์š”.
๋„ˆ๋ฌด ์ข‹์€ ์ž๋ฃŒ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋„์›€ ๋งŽ์ด ๋๊ณ  v5 ๊ด€๋ จ ์—…๋ฐ์ดํŠธ๋„ ๊ธฐ๋Œ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๊ณต์‹๋ฌธ์„œ์™€ ํ•จ๊ป˜ ์ฝ์œผ๋ฉฐ ๊ณต๋ถ€ํ•˜๋˜ ๋„์ค‘์— ์ฟผ๋ฆฌ ๋ฌดํšจํ™” ๋ถ€๋ถ„์—์„œ ์˜๋ฌธ์Šค๋Ÿฌ์šด ๋ถ€๋ถ„์ด ์žˆ์–ด์„œ ์ด์Šˆ ๋‚จ๊ฒจ๋ด…๋‹ˆ๋‹ค.

๋ฌดํšจํ™” ํ•˜๋ ค๋Š” ํ‚ค๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ธ ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์„ค๋ช…์ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜์™€์žˆ๋Š”๋ฐ์š”,

  • ๋งŒ์•ฝ ๋ฌดํšจํ™” ํ•˜๋ ค๋Š” ํ‚ค๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด ์•„๋ž˜ ์˜ˆ์ œ์™€ ๊ฐ™์ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐฐ์—ด๋กœ ๋ณด๋‚ด์ฃผ๋ฉด ๋œ๋‹ค.
    queryClient.invalidateQueries(["super-heroes", "posts", "comment"]);

ํ•˜์ง€๋งŒ, ๊ณต์‹๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด invalidateQueries์™€ ๊ด€๋ จํ•˜์—ฌ, ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ฟผ๋ฆฌ๋ฅผ prefix๋ฅผ ์ด์šฉํ•ด์„œ ๋ฌดํšจํ™” ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋‚˜์˜ต๋‹ˆ๋‹ค.
์ฆ‰, queryClient.invalidateQueries(["super-heroes"]); ๋Š” ["super-heroes"]๊ฐ€ prefix์ธ ์ฟผ๋ฆฌํ‚ค๋ฅผ ๊ฐ–๋Š” ๋ชจ๋“  ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํšจํ™” ์‹œํ‚ค๊ณ , queryClient.invalidateQueries(["super-heroes", "posts", "comment"])๋Š” ์˜คํžˆ๋ ค ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ‚ค๋ฅผ ๋ฌดํšจํ™” ์‹œํ‚ค๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ["super-heroes", "posts", "comment"]๋ผ๋Š” ์ •ํ™•ํ•œ ํ‚ค ํ•˜๋‚˜์— ํ•ด๋‹นํ•˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํšจํ™” ์‹œํ‚จ๋‹ค๊ณ  ๋ณด๋Š” ๊ฒŒ ๋งž๋Š” ๊ฑฐ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ณต์‹๋ฌธ์„œ Query Matching with invalidateQueries

๋งŒ์•ฝ์— ๋…๋ฆฝ์ ์ธ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ฟผ๋ฆฌํ‚ค๋ฅผ ๋ฌดํšจํ™” ์‹œํ‚ค๊ณ  ์‹ถ๋‹ค๋ฉด

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

const useAddSuperHeroData = () => {
  const queryClient = useQueryClient();
  return useMutation(addSuperHero, {
    onSuccess(data) {
      queryClient.invalidateQueries(["super-heroes"]);
      queryClient.invalidateQueries(["posts"]);
      queryClient.invalidateQueries(["comment"]); 
    },
    onError(err) {
      console.log(err);
    },
  });
};

์œ„์™€ ๊ฐ™์ด invalidateQueries๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ์ ˆํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ์•„๋ž˜๋Š” ๊ณต์‹๋ฌธ์„œ์— ์‚ฌ์šฉ๋œ mutation์‹œ onSuccess ์ฝœ๋ฐฑ์—์„œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ฟผ๋ฆฌํ‚ค๋ฅผ ๋ฌดํšจํ™” ์‹œํ‚ค๋Š” ์˜ˆ์ œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
๊ณต์‹๋ฌธ์„œ Invalidations from Mutations

import { useMutation, useQueryClient } from '@tanstack/react-query'

const queryClient = useQueryClient()

// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
const mutation = useMutation({
  mutationFn: addTodo,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
    queryClient.invalidateQueries({ queryKey: ['reminders'] })
  },
})

ํ˜น์‹œ ์ œ๊ฐ€ ๋ฌธ์„œ๋ฅผ ์ž˜๋ชป ์ดํ•ดํ•˜๊ณ  ์žˆ์—ˆ๋‹ค๋ฉด ๋ง์”€ํ•ด์ฃผ์„ธ์š”.
๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

master -> main

default branch๋ฅผ main์œผ๋กœ ์˜ฎ๊ธฐ๋ฉด ์–ด๋–จ๊นŒ์š”?

๋ชฉ์ฐจ์— ์ ํžŒ cancelQueries ๋‚ด์šฉ

์•ˆ๋…•ํ•˜์„ธ์š”!

์ฟผ๋ฆฌ ์ˆ˜๋™ ๋ฌดํšจํ™” cancelQueries

๋ชฉ์ฐจ 24๋ฒˆ์— ํ•ด๋‹นํ•˜๋Š” ์œ„ ๋‚ด์šฉ์—์„œ ๋ฌดํšจํ™”๋ฅผ ์ทจ์†Œ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒŒ ๋” ์ •ํ™•ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง„๋‹ค๊ณ  ์ƒ๊ฐํ•ด ์˜๊ฒฌ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ (optimistic update) ์ฝ”๋“œ ์˜ค๋ฅ˜

์•ˆ๋…•ํ•˜์„ธ์š”!
Tanstack Query v5์— ๋Œ€ํ•œ ์ข‹์€ ๋ฌธ์„œ ์ œ๊ณต์— ํ•ญ์ƒ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
๋ฌธ์„œ๋ฅผ ๋ณด๋˜ ๋„์ค‘ ํ•œ๊ฐ€์ง€ ์˜ค๋ฅ˜๋กœ ๋ณด์ด๋Š” ๋ถ€๋ถ„์„ ์ฐพ์•„ ์ด์Šˆ ๋‚จ๊ฒผ์Šต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)์„ ์˜ฌ๋ ค์ฃผ๋Š” Optimistic Updates(๋‚™๊ด€์  ์—…๋ฐ์ดํŠธ)

onMutate: async (newHero) => { ... } ์˜ ํ˜•ํƒœ๋กœ argument๋ฅผ ํ•จ์ˆ˜๋กœ ๋„˜๊ฒจ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

onMutate: async () => {
  await queryClient.cancelQueries(["super-heroes"]);

  const previousHeroData = queryClient.getQueryData(["super-heroes"]);

  queryClient.setQueryData(["super-heroes"], (oldData: any) => {
    return {
      ...oldData,
      data: [...oldData.data, { ...newHero, id: oldData?.data?.length + 1 }],
    };
  });

  return { previousHeroData };
},

ํ˜„์žฌ ์ฝ”๋“œ๋Š” ์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ newHero๋ฅผ ๋ฐ›์•„์˜ค๋Š” argument๊ฐ€ ๋น ์ ธ ์žˆ์–ด ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •์ด ์ด๋ฃจ์–ด์ ธ์•ผ ํ•  ๋“ฏ ํ•ฉ๋‹ˆ๋‹ค.

// mutate๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋ฐ›์€ newHero๋ฅผ onMutate ์˜ ํ•จ์ˆ˜ argument๋กœ ๋ฐ›์•„์•ผ ํ•จ.
onMutate: async (newHero) => {
  await queryClient.cancelQueries(["super-heroes"]);

  const previousHeroData = queryClient.getQueryData(["super-heroes"]);

  queryClient.setQueryData(["super-heroes"], (oldData: any) => {
    return {
      ...oldData,
      data: [...oldData.data, { ...newHero, id: oldData?.data?.length + 1 }],
    };
  });

  return { previousHeroData };
},

์บ์‹ฑ ๋ผ์ดํ”„ ์‚ฌ์ดํด ์„ค๋ช… ๋ณด์™„

์•ˆ๋…•ํ•˜์„ธ์š”, ๋ฌธ์„œ ์ž˜ ๋ณด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋ณด๋‹ค๋ณด๋‹ˆ ์กฐ๊ธˆ ํ‘œํ˜„์ด ๋ถˆ๋ช…ํ™•ํ•œ ๋ฌธ์žฅ์ด ์žˆ๋Š”๊ฒƒ ๊ฐ™์•„ ์•Œ๋ ค๋“œ๋ฆฌ๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

์บ์‹ฑ ๋ผ์ดํ”„ ์‚ฌ์ดํด์˜ 6๋ฒˆ ๋ฌธ์žฅ
6. ๋งŒ์ผ, gcTime ์ง€๋‚˜๊ธฐ ์ „์ด๊ณ , A ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค freshํ•œ ์ƒํƒœ๋ผ๋ฉด ์ƒˆ๋กญ๊ฒŒ mount๋˜๋ฉด ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.
์ด ๋ฌธ์žฅ์˜ ์„ค๋ช…์ด ์กฐ๊ธˆ ํ—ท๊ฐˆ๋ฆฌ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์ผ, gcTime์ด ์ง€๋‚˜๊ธฐ ์ „์ด๊ณ , A ์ฟผ๋ฆฌ ์ธ์Šคํ„ด์Šค๊ฐ€ staleํ•œ ์ƒํƒœ๋ผ๋ฉด ๋‹ค์‹œ mount๋˜๋ฉด ์šฐ์„  ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.
์ดํ›„, ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ž๋™์œผ๋กœ refetch๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ์ƒˆ ๋ฐ์ดํ„ฐ๋กœ ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ณ , ์ด๋ฅผ ๋ฐ˜์˜ํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ค€๋‹ค.
์œ„ ์„ค๋ช…์ด ๋” ๋ช…ํ™•ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ์˜ ์ข…์†์„ฑ (Data Dependency)

useCallback ๋Œ€์‹ ํ•ด์„œ useQuery์— ๋“ค์–ด์˜ค๋Š” [ ] ์ข…์†์„ฑ ๋ฐฐ์—ด์€ ๋˜‘๊ฐ™์€ ๊ธฐ๋Šฅ์„ ํ•˜๊ธฐ์— ๋”์ด์ƒ Callback์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

ํ˜น์‹œ ์ ์–ด์ ธ ์žˆ๋‚˜์š” ?
์ฐพ๋‹ค๋ณด๋‹ˆ.. ์ด๋Ÿฌํ•˜๋„ค์š” ..

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.