๐Ÿ’ก 01. Infinite Queries์™€ ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊นŠ๊ฒŒ ํŒŒ๋ณด๊ธฐ

2026๋…„ 3์›” 4์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

useInfiniteQuery์˜ ๋™์ž‘ ์›๋ฆฌ์™€ Cursor ๊ธฐ๋ฐ˜ ๋ฌดํ•œ ์Šคํฌ๋กค ๊ตฌํ˜„๋ฒ•, ๊ทธ๋ฆฌ๊ณ  select ์˜ต์…˜์„ ํ™œ์šฉํ•œ ๋ฐ์ดํ„ฐ ๋‹ค์ด์–ดํŠธ ๋ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ

"์˜์ฒ  ๋‹˜, ๋ฌดํ•œ ์Šคํฌ๋กค ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด 5๊ฐœ ์ด์–ด ๋ถ™์ธ๋‹ค๊ณ  ํ”„๋ก ํŠธ ๋ฐฐ์—ด ๋ณต์‚ฌ(concat)๋กœ ์—ฐ์‚ฐ ๋‹ค ๋Œ๋ฆฌ๊ณ  ๊ณ„์‹ ๊ฐ€์š”?"

โ˜•๏ธ ์˜์ฒ ์ด์˜ ๊ณ ๋ฏผ: "๋ฌดํ•œ ์Šคํฌ๋กค, ๋ Œ๋”๋ง์ด ๊ฐˆ์ˆ˜๋ก ๋А๋ ค์ ธ์š”!"

(์›”์š”์ผ ์˜คํ›„, ๋์—†์ด ๋‚ด๋ ค๊ฐ€๋Š” ์‡ผํ•‘๋ชฐ ์ƒํ’ˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด๋ฉฐ ํ•œ์ˆจ ์‰ฌ๋Š” ์˜์ฒ )

๐Ÿฃ ์˜์ฒ : ๋ฆฌ๋“œ ๋‹˜! ์ € ๋“œ๋””์–ด ์ปค๋ฎค๋‹ˆํ‹ฐ ์•ฑ ๋ฉ”์ธ ํ”ผ๋“œ์— ๋ฌดํ•œ ์Šคํฌ๋กค์„ useQuery๋กœ ๋‹ฌ์•˜์Šต๋‹ˆ๋‹ค.
ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ(pageState) 1์”ฉ ์˜ฌ๋ฆด ๋•Œ๋งˆ๋‹ค ๊ธฐ์กด ๋ฐฐ์—ด์— [...old, ...newData] ์ด๋ ‡๊ฒŒ ํ•ฉ์ณ์„œ State๋กœ ์ €์žฅํ•˜๊ณ , ๊ทธ๊ฑธ map ๋Œ๋ ค์„œ ๊ทธ๋ฆฌ๊ณ  ์žˆ์–ด์š”. ๊ทผ๋ฐ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•œ 1,000๊ฐœ ๋„˜์–ด๊ฐ€๋‹ˆ๊นŒ ํœ  ๋‚ด๋ฆด ๋•Œ๋งˆ๋‹ค ๋ Œ๋”๋ง์ด ํ„ฑํ„ฑ ๊ฑธ๋ฆฌ๋„ค์š” ใ… ใ… .

๐Ÿฆ ์˜ํ˜ธ: ์˜์ฒ  ๋‹˜, ๋ฐ์ดํ„ฐ ํŒจ์นญ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค 1000 + 20 ๊ฐœ์˜ ๊ฑฐ๋Œ€ํ•œ ๋ฐฐ์—ด ๋ญ‰ํ……์ด๋ฅผ ํ†ต์งธ๋กœ ๋”ฅ ์ปดํŽ˜์–ด(Deep Compare) ๋Œ๋ฆฌ๊ณ , ๊ทธ๊ฑธ ๋˜ ๋กœ์ปฌ useState ์— ๋ณต์‚ฌํ•ด์„œ ๋ฐ€์–ด๋„ฃ์œผ๋‹ˆ๊นŒ ๋ธŒ๋ผ์šฐ์ € ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ„ฐ์ ธ๋‚˜๊ฐ€๋Š” ๊ฒ๋‹ˆ๋‹ค.
๋ฌดํ•œ ์Šคํฌ๋กค์€ useQuery ํ•˜๋‚˜๋กœ ๋ฐฐ์—ด์„ ๋ง๋Œ€๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, useInfiniteQuery ์™€ select ์˜ต์…˜์˜ ์–‘๋‚  ๊ฒ€์œผ๋กœ ์บ์‹œ ํฌ์ธํ„ฐ๋งŒ ๋Š˜๋ ค๊ฐ€๋Š” ์•„์ฃผ ์šฐ์•„ํ•œ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€: ๋ฐฐ์—ด์˜ ํ•จ์ •๊ณผ ๋‹ค์ด์–ดํŠธ

์ˆ˜๋งŽ์€ ๋ฐ์ดํ„ฐ(์˜ˆ: ํŠธ์œ„ํ„ฐ ํƒ€์ž„๋ผ์ธ, ์ธ์Šคํƒ€ ํ”ผ๋“œ)๋ฅผ ์œ ์ €์—๊ฒŒ ํ•œ ๋ฒˆ์— ์˜๋Š” ๊ฑด ์„œ๋ฒ„๋น„์˜ ๋‚ญ๋น„์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” Cursor(๋งˆ์ง€๋ง‰์œผ๋กœ ๋ณธ ๊ธ€ ๋ฒˆํ˜ธ)๋‚˜ Page(ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ)๋ฅผ ๊ฑด๋„ค์„œ 20๊ฐœ์”ฉ ์ชผ๊ฐœ ๋ฐ›์Šต๋‹ˆ๋‹ค(Pagination).

๋ฌธ์ œ๋Š” ํ”„๋ก ํŠธ์—”๋“œ์ž…๋‹ˆ๋‹ค. ๊ณ„์† ๋ฐ›์•„์˜ค๋Š” ๊ฐ์ฒด ๋ฉ์–ด๋ฆฌ๋“ค์„ ์–ด๋””์— ์บ์‹ฑํ•˜๊ณ , ์–ด๋–ป๊ฒŒ ํ•ฉ์น  ๊ฒƒ์ธ๊ฐ€?
React Query์˜ useInfiniteQuery๋Š” ์ด ํŽ˜์ด์ง• ์Šค๋ƒ…์ƒท๋“ค์„ ๋ฐฐ์—ด ์•ˆ์˜ ๋ฐฐ์—ด(pages[0], pages[1])๋กœ ์บ์‹œ์— ์Œ“์•„๋‘๊ณ , ๋‹ค์Œ ํŽ˜์ด์ง€(Next Cursor)๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ž๊ธฐ ์Šค์Šค๋กœ ์ถ”์ (Tracking)ํ•˜๋Š” ์™„์ „ ๊ด€๋ฆฌํ˜• ํ›…์ž…๋‹ˆ๋‹ค.

๋˜ํ•œ ์—ฌ๊ธฐ์— ๋ฐ์ดํ„ฐ ์‚ฌ์ด์ฆˆ๋ฅผ ๊นŽ์•„์ฃผ๋Š” ๊ถ๊ทน๊ธฐ select ์˜ต์…˜์ด ๊ฒฐํ•ฉ๋˜๋ฉด, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฒ„๋ฆด ๋ฐ์ดํ„ฐ๋Š” ์• ์ดˆ์— ์บ์‹œ ๊ตฌ๋… ๊ณ„์ธต์—์„œ๋ถ€ํ„ฐ ์ž˜๋ผ๋ฒ„๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


1. useInfiniteQuery ํ•ด๋ถ€ํ•™ (Cursor ๊ธฐ๋ฐ˜)

TkDodo์˜ ์•„ํ‹ฐํด #26์„ ๋ณด๋ฉด, ๋ฌดํ•œ ์ฟผ๋ฆฌ๋Š” ์‚ฌ์‹ค "ํŽ˜์ด์ง€๋ณ„ ์Šค๋ƒ…์ƒท์„ ๋‹ด์€ 2์ฐจ์› ๋ฐฐ์—ด"์ž…๋‹ˆ๋‹ค.

// ๐Ÿ“ types.ts
export type Post = { id: number; title: string; content: string };
export type FetchPostsResponse = {
  data: Post[];
  nextCursor: number | null; // ์„œ๋ฒ„์—์„œ "๋‹ค์Œ ํ˜ธ์ถœ์€ ์ด ๋ฒˆํ˜ธ๋ถ€ํ„ฐ ํ•ด!" ๋ผ๊ณ  ์คŒ
};

๊ฐ€์žฅ ๊ธฐ์ดˆ์ ์ด๋ฉด์„œ ์™„๋ฒฝํ•œ TypeScript ๊ธฐ๋ฐ˜ ๋ฌดํ•œ ์ฟผ๋ฆฌ ์˜ต์…˜ ๊ฐ์ฒด๋ฅผ ์žก์•„๋ด…์‹œ๋‹ค.

// ๐Ÿ“ hooks/queries/useInfinitePosts.ts
import { useInfiniteQuery } from '@tanstack/react-query';
import axios from 'axios';
import { FetchPostsResponse } from '@/types';
 
export const useInfinitePosts = () => {
  return useInfiniteQuery({
    queryKey: ['posts', 'infinite'],
    // 1๏ธโƒฃ pageParam: ์ด๊ฒŒ ๋ฐ”๋กœ Next.js์˜ Cursor์ž…๋‹ˆ๋‹ค. ์ตœ์ดˆ์—” ๋ฌด์กฐ๊ฑด ์ฒซ ํŽ˜์ด์ง€(0)๋กœ ์‹œ์ž‘.
    queryFn: async ({ pageParam = 0 }) => {
      const res = await axios.get<FetchPostsResponse>(`/posts?cursor=${pageParam}`);
      return res.data;
    },
    // 2๏ธโƒฃ TypeScript์˜ ์œ„์—„: ์ดˆ๊ธฐ ์ปค์„œ๊ฐ’์„ ํ•„์ˆ˜๋กœ ์ •์˜ (v5 ๊ธฐ๋Šฅ)
    initialPageParam: 0,
    // 3๏ธโƒฃ ํ•ต์‹ฌ ๋งˆ๋ฒ•: ๋ฐฉ๊ธˆ ์‘๋‹ต๋ฐ›์€ ๋ฐ์ดํ„ฐ(lastPage)๋ฅผ ๊นŒ๋ณด๊ณ , ๋‹ค์Œ ์ปค์„œ๊ฐ€ ์žˆ์œผ๋ฉด ๋ฆฌํ„ด!
    getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
  });
};

์ด์ œ ์ปดํฌ๋„ŒํŠธ๋Š” ์ƒํƒœ ๋ณ€์ˆ˜(page=1)๋‚˜ setPage๋ฅผ ๋“ค๊ณ  ์žˆ์„ ํ•„์š”๊ฐ€ ์ผ์ ˆ ์—†์Šต๋‹ˆ๋‹ค.
์˜ค์ง ํ›…์ด ์ฃผ๋Š” fetchNextPage() ํ•จ์ˆ˜ ํ•˜๋‚˜๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋๋‚ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด์— ๋ถ™์ผ ๋•Œ๋Š” data.pages.map์œผ๋กœ ํ’€์–ด์„œ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.

function Feed() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfinitePosts();
 
  return (
    <div>
      {/* data.pages๋Š” [ {data:[20๊ฐœ], next:1}, {data:[20๊ฐœ], next:2} ] ํ˜•ํƒœ์˜ 2์ฐจ์› ๋ฐฐ์—ด์ด๋‹ค. */}
      {data?.pages.map((page, index) => (
        <React.Fragment key={index}>
          {page.data.map(post => <PostCard key={post.id} post={post} />)}
        </React.Fragment>
      ))}
 
      <button 
        onClick={() => fetchNextPage()} 
        disabled={!hasNextPage || isFetchingNextPage}
      >
        {isFetchingNextPage ? '๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...' : hasNextPage ? '๋” ๋ณด๊ธฐ' : '๋งˆ์ง€๋ง‰ ๊ธ€์ž…๋‹ˆ๋‹ค'}
      </button>
    </div>
  )
}

๐Ÿ”ฅ ์‹œ๋‹ˆ์–ด์˜ ์กฐ์–ธ:
์‹ค๋ฌด์—์„œ๋Š” ๋” ๋ณด๊ธฐ ๋ฒ„ํŠผ ๋Œ€์‹  IntersectionObserver ๋ฅผ ์ด์šฉํ•ด ๋ฐ”๋‹ฅ์— ๋‹ฟ์œผ๋ฉด(ViewPort์— ๋“ค์–ด์˜ค๋ฉด) fetchNextPage() ๋ฅผ ์ž๋™์œผ๋กœ ๋ฐœ์‚ฌํ•˜๊ฒŒ๋” ๋งŒ๋“ค์–ด ์ง„์งœ ๋ฌดํ•œ ์Šคํฌ๋กค์„ ์™„์„ฑํ•ฉ๋‹ˆ๋‹ค. (react-intersection-observer ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”์ฒœ)


2. ๋ฐ์ดํ„ฐ ๋‹ค์ด์–ดํŠธ ๋ํŒ์™•: select ์˜ต์…˜

์ž, ์˜์ฒ ์ด์˜ "๋ Œ๋”๋ง ํ„ฑํ„ฑ ๊ฑธ๋ ค์š”" ๋ฌธ์ œ๊ฐ€ ์•„์ง ์•ˆ ํ’€๋ ธ์ฃ ?
์„œ๋ฒ„์—์„œ Post ์ •๋ณด๋กœ title, content ๋งŒ ์˜ค๋ฉด ๋‹คํ–‰์ธ๋ฐ... ์œ ์ € ์ •๋ณด ๋ญ‰ํƒœ๊ธฐ, ์ข‹์•„์š” ๋ˆ„๋ฅธ ์œ ์ € ๋ฐฐ์—ด, ์ด๋ฏธ์ง€ URL ์Šค๋ฌด ๊ฐœ, ์“ธ๋ฐ์—†๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊นŒ์ง€ ํ†ต์งธ๋กœ ๋‚ด๋ ค์˜จ๋‹ค๋ฉด? 1,000๊ฐœ๋ฅผ ๋ชจ์•„ ๊ทธ๋ฆด ๋•Œ ๋ธŒ๋ผ์šฐ์ € ๋ Œ๋”๊ฐ€ ๋ฌด๊ฑฐ์›Œ์ง‘๋‹ˆ๋‹ค.

๊ฒŒ๋‹ค๊ฐ€ 2์ฐจ์› ๋ฐฐ์—ด(data.pages.map)์„ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ๋„ ์€๊ทผํžˆ ์ฝ”๋“œ ๋ށ์Šค๊ฐ€ ๊นŠ์–ด์ ธ์„œ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง‘๋‹ˆ๋‹ค.
์šฐ๋ฆฌ๋Š” React Query์˜ select ์˜ต์…˜ ์œผ๋กœ ์ด ์›์‹œ API ๋ฐ์ดํ„ฐ๋ฅผ "๋‚ด ์ž…๋ง›์— ๋งž๋Š” ๊ฐ€๋ฒผ์šด 1์ฐจ์› ๋ Œ๋”๋ง ๋ฐฐ์—ด"๋กœ ๊นŽ์•„๋ฒ„๋ฆด ๊ฒ๋‹ˆ๋‹ค!

export const useOptimizedInfinitePosts = () => {
  return useInfiniteQuery({
    queryKey: ['posts', 'infinite'],
    queryFn: fetchInfinitePosts,
    initialPageParam: 0,
    getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
    // ๐Ÿš€ ๋ฐ์ดํ„ฐ ํญํ’ ๋‹ค์ด์–ดํŠธ & 1์ฐจ์› ๋ฐฐ์—ด(Flat) ํ™”!
    select: (data) => ({
      // pages (2์ฐจ์›) ๋ฐฐ์—ด์„ flatMap์œผ๋กœ 1์ฐจ์›์œผ๋กœ ์ซ™ ํŽผ์นจ
      // ๊ทธ๋ฆฌ๊ณ  ๋ฌด๊ฑฐ์šด content ๋‹ค ๋ฒ„๋ฆฌ๊ณ  ๋”ฑ id, title๋งŒ ๋ฉ”๋ชจ๋ฆฌ ๋ Œ๋”๋กœ ์˜ฌ๋ ค๋ณด๋ƒ„
      pages: data.pages.flatMap((page) => 
        page.data.map((post) => ({
          id: post.id,
          title: post.title,
        }))
      ),
      pageParams: data.pageParams,
    }),
  });
};

select ํ•จ์ˆ˜ ๋‚ด๋ถ€์˜ ์—ฐ์‚ฐ์€ React Query๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ๊ทนํ•œ์˜ Memoization(์บ์‹ฑ) ์„ ๋Œ๋ ค์ค๋‹ˆ๋‹ค.
์›๋ณธ ๋ฐ์ดํ„ฐ(์บ์‹œ ๊ธˆ๊ณ )์˜ ๋ฐ์ดํ„ฐ 1๊ฐœ๋ผ๋„ ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ์ด์ƒ ์ ˆ๋Œ€ flatMap์„ ๋‹ค์‹œ ์—ฐ์‚ฐํ•˜์ง€ ์•Š์•„์š”!
๋•๋ถ„์— ์ปดํฌ๋„ŒํŠธ ์ชฝ์—์„œ๋Š” ์•„๋ฌด ์ƒ๊ฐ ์—†์ด ๊ฐ€๋ฒผ์›Œ์ง„ data.pages.map ๋งŒ 1์ฐจ์› ๋ฐฐ์—ด๋กœ ํŽธ์•ˆํ•˜๊ฒŒ ์ฆ๊ธฐ๋ฉด ๋ Œ๋”๋ง ์Šค๋ชฐ ๋‹ค์ด์–ดํŠธ๊ฐ€ ์™„์„ฑ๋˜๋Š” ๊ฒ๋‹ˆ๋‹ค.


๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์™€... ์˜ค๋Š˜ select ์˜ต์…˜์— flatMap ํƒœ์›Œ์„œ ๋ฐ์ดํ„ฐ ๊ฐ€๋ณ๊ฒŒ ๊นŽ์•„๋ƒˆ๋”๋‹ˆ, ๋ฌดํ•œ ์Šคํฌ๋กค 1,000๊ฐœ ๋‚ด๋ ค๊ฐ€๋˜ ๊ฑฐ ํ”„๋ ˆ์ž„ ์™„์ „ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๊ฝ‚ํžŒ๋‹ค ๋ฏธ์ณค๋‹ค ์ง„์งœ ใ… ใ… 

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "๋ฌดํ•œ ์Šคํฌ๋กค์€ ๋‚ด๊ฐ€ ๋ฐฐ์—ด ๋ณต์‚ฌ(Local State) ํ•ด์„œ ๋ถ™์ด๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ useInfiniteQuery์—๊ฒŒ ์ปค์„œ ํฌ์ธํ„ฐ๋งŒ ๋˜์ ธ์ฃผ๋Š” ๊ฑฐ๋‹ค! ๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ๋น„๋Œ€ํ•ด์งˆ ๋• select ๊ณผ๋„๋กœ ๋ทฐ ์ „์šฉ ๊ฐ์ฒด๋กœ ๋‚ ์นด๋กญ๊ฒŒ ์กฐ๋ฆฝ(Memoize) ์‹œ์ผœ์„œ UI๋ฅผ ๊ตฌ์ถœํ•˜์ž."

์˜ˆ์ „์—” useEffect ์•ˆ์—์„œ ๋ฐฐ์—ด ์ž๋ฅด๊ณ  ๋ถ™์ด๊ณ  ์Šคํฌ๋กค ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ ๊ฑธ๊ณ  ์•„์ฃผ ์ŠคํŒŒ๊ฒŒํ‹ฐ ์š”๋ฆฌ์‚ฌ๊ฐ€ ๋”ฐ๋กœ ์—†์—ˆ๋Š”๋ฐ, ์—ญ์‹œ ๋„๊ตฌ๊ฐ€ ์ข‹์œผ๋‹ˆ๊นŒ ๋กœ์ง์ด ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ 10์ค„ ์ด๋‚ด๋กœ ์••์ถ•๋˜๋„ค. ๋ฌดํ•œ ์Šคํฌ๋กค ๋ฌด์„œ์› ๋Š”๋ฐ ์ด์   ์Šคํฌ๋กค ๋ฐ”๋‹ฅ ์น  ๋•Œ๋งˆ๋‹ค ์งœ๋ฆฟํ•˜๋‹ค! ๋‚ด์ผ ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜ํ•œํ…Œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์™„์ „ ๋‹น๋‹นํžˆ ์˜ฌ๋ ค์•ผ์ง€ ๐Ÿ˜†.


๐Ÿ“ ๋ฐฐ์šด ๋‚ด์šฉ ์ ๊ฒ€ํ•˜๊ธฐ (Quiz)

Q. ์˜์ฒ ์ด๊ฐ€ ์‡ผํ•‘๋ชฐ ๋ฌดํ•œ ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•˜๋˜ ์ค‘, ํŠน์ • ์ƒํ’ˆ ํ•˜๋‚˜๊ฐ€ ์‚ญ์ œ๋˜๊ฑฐ๋‚˜ ํ’ˆ์ ˆ๋กœ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ์•ก์…˜(useMutation)์ด ์ผ์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ, ๋ฌดํ•œ ์Šคํฌ๋กค ํ˜•ํƒœ์˜ ๊ฑฐ๋Œ€ํ•œ ๋‹ค์ฐจ์› ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ ์ค‘ ๋”ฑ ๊ทธ ํ•˜๋‚˜์˜ ์ƒํ’ˆ ์ƒํƒœ๋งŒ (์ „์ฒด ๋ฆฌํŒจ์นญ ์—†์ด) '๋‚™๊ด€์  ๋ฎ์–ด์“ฐ๊ธฐ(setQueryData)'๋กœ ๊ณ ์ณ ์น˜๋ ค๋ฉด, TypeScript ํ™˜๊ฒฝ์—์„œ ์–ด๋–ป๊ฒŒ ์ฝœ๋ฐฑ์„ ์ž‘์„ฑํ•ด์•ผ ์•ˆ์ „ํ• ๊นŒ์š”?

  • A) queryClient.setQueryData(['posts', 'infinite'], (old) => { ... }) ์•ˆ์—์„œ 1์ฐจ์› ๋ฐฐ์—ด๋กœ filter ์น˜๊ณ  ๋Œ๋ ค์ค€๋‹ค.
  • B) ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ๋ฌดํ•œ ์Šคํฌ๋กค ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ(pages, pageParams)๋Š” ๋„ˆ๋ฌด ๋ณต์žกํ•ด์„œ ๋ถ€๋ถ„ ์ˆ˜์ •์ด ๊ผฌ์ด๋ฏ€๋กœ ๋ฌด์กฐ๊ฑด invalidateQueries๋กœ ์ „์ฒด ์ดˆ๊ธฐ๋ถ€ํ„ฐ ์žฌํ†ต์‹ ํ•ด์•ผ๋งŒ ํ•œ๋‹ค.
  • C) setQueryData ์˜ old ํด๋กœ์ € ์•ˆ์—์„œ old.pages ๋ฐฐ์—ด์„ map ์ˆœํšŒํ•˜์—ฌ page ๋ญ‰์น˜๋ฅผ ์—ด๊ณ , ๊ทธ ์•ˆ์˜ page.data ๋ฐฐ์—ด์„ ํ•œ ๋ฒˆ ๋” map์œผ๋กœ ์ฐพ์•„ ํ•ด๋‹น ์ƒํ’ˆ๋งŒ ์ˆ˜์ •ํ•ด์ค€ ๋˜‘๊ฐ™์€ 2์ฐจ์› ๊ฐ์ฒด ๊ตฌ์กฐ({ pages, pageParams })๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

โœ… ์ •๋‹ต: C

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: TkDodo ์•„ํ‹ฐํด #26๊ณผ #2๋ฅผ ์‚ดํŽด๋ณด๋ฉด useInfiniteQuery์˜ ์บ์‹œ ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ ์›๋ณธ์€ ํ•ญ์ƒ { pages: [...], pageParams: [...] } ํ˜•ํƒœ์˜ ๊ธฐ๊ดดํ•œ ๊ฐ์ฒด ๋ฉ์–ด๋ฆฌ์ž…๋‹ˆ๋‹ค. ์ˆ˜๋™์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ชผ๊ฐœ ์ˆ˜์ •(setQueryData) ํ•  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ๊ธฐ์กด์˜ 2์ฐจ์›(ํŽ˜์ด์ง€ ์•ˆ์˜ ์•„์ดํ…œ) ๋ฐฐ์—ด ๊ตฌ์กฐ๋ฅผ ํ›ผ์†ํ•˜์ง€ ์•Š๊ณ  ์™„๋ฒฝํ•˜๊ฒŒ ๋™์ผํ•œ ๊ป๋ฐ๊ธฐ๋กœ ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜ํ•ด ์ฃผ์–ด์•ผ React Query๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ฟœ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: "์˜์ฒ  ๋‹˜! A๋ฒˆ์ฒ˜๋Ÿผ 1์ฐจ์› ์Œฉ ๋ฐฐ์—ด๋กœ ๋ฆฌํ„ดํ•ด๋ฒ„๋ฆฌ๋ฉด ์บ์‹œํ†ต ๊ตฌ์กฐ๊ฐ€ ํ„ฐ์ ธ์„œ ๋‹ค์Œ ๋ฌดํ•œ ์Šคํฌ๋กค ํŽ˜์นญํ•  ๋•Œ ํฌ๋ž˜์‹œ ์—๋Ÿฌ๊ฐ€ ํญ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ๊ตฌ์กฐ์  ๊ป๋ฐ๊ธฐ๋Š” ํ•ญ์ƒ ์œ ์ง€ํ•ด์•ผ ํ•ด์š”!"
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ๋ฌดํ•œ ๋ฌดํšจํ™” ๋ฎ์–ด์“ฐ๊ธฐ(setQueryData) ํ•  ๋•Œ๋Š”, pages ๊ฐ์ฒด ๊ตฌ์กฐ ๊ทธ๋Œ€๋กœ ์ด์ค‘๋ฃจํ”„(map ์•ˆ์˜ map)๋กœ ๊นŒ๊ณ  ๋‹ซ๊ธฐ!