๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป ํ”„๋กœ์ ํŠธ/๐Ÿ€ํ–‰์šด์˜ ๋กœ๋˜ ๋งˆ๋ฒ•์ง„๐Ÿ›ธ

[React, Next.js] TanStackQuery, ์„ค์น˜ํ•˜๊ณ  ๋ฆฌ์•กํŠธ ์ฝ”๋“œ์— ์ ์šฉํ•˜๊ธฐ!

by hyeong._.ing 2026. 5. 12.

 

์‹ค์ œ๋กœ ์„œ๋ฒ„๋ฅผ ์šด์˜ํ•  ์ƒ๊ฐ์ด๊ธฐ์—
์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด
TanStackQuery ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋„ฃ์–ด๋ณผ ์ƒ๊ฐ์ด๋‹ค.

 

 

 

 

https://lotto-magic-frontend.vercel.app/

 

๋กœ๋˜ ๋ฒˆํ˜ธ ์ƒ์„ฑ ๋งˆ๋ฒ•์ง„

ํ–‰์šด ์š”์†Œ 3๊ฐœ๋ฅผ ์„ ํƒํ•˜๋ฉด ์˜ค๋Š˜์˜ ๋กœ๋˜ ๋ฒˆํ˜ธ์™€ ํ–‰์šด ์ ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์‚ฌ์ดํŠธ์ž…๋‹ˆ๋‹ค.

lotto-magic-frontend.vercel.app

 

 

 

 

 

 

1. TanStackQuery

  • ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.
  • ๊ทธ ์™ธ ๋ฐ์ดํ„ฐ ์บ์‹ฑ, ์บ์‹œ ์ œ์–ด ๋“ฑ ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ณ  ํšจ์œจ์ ์ด๊ฒŒ ๊ด€๋ฆฌํ•ด์ค€๋‹ค.
  • React Query๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์‹œ์ž‘ํ•˜์ง€๋งŒ v4๋ถ€ํ„ฐ Vue๋‚˜ Svelte ๋“ฑ์˜ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ๋„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ธฐ๋Šฅ์ด ํ™•์žฅ๋˜๋ฉฐ TanStack Query๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.
  • ๋Œ€ํ‘œ์ ์ธ ๊ธฐ๋Šฅ์œผ๋กœ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ ๋ฐ ์บ์‹ฑ | ๋™์ผ ์š”์ฒญ์˜ ์ค‘๋ณต ์ œ๊ฑฐ | ์‹ ์„ ํ•œ ๋ฐ์ดํ„ฐ ์œ ์ง€ | ๋ฌดํ•œ ์Šคํฌ๋กค, ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋“ฑ์˜ ์„ฑ๋Šฅ ์ตœ์ ํ™” | ๋„คํŠธ์›Œํฌ ์žฌ์—ฐ๊ฒฐ, ์š”์ฒญ ์‹คํŒจ ๋“ฑ์˜ ์ž๋™ ๊ฐฑ์‹ ์ด ์žˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ์บ์‹ฑ
    : TanStack Query๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ๋Š” ํ•ญ์ƒ ์ฟผ๋ฆฌ ํ‚ค(queryKey)๋ฅผ ์ง€์ •ํ•˜๊ฒŒ ๋œ๋‹ค. ์ฟผ๋ฆฌ ํ‚ค์™€ ์ผ์น˜ํ•˜๋Š” ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์„ ๋•Œ, ์„œ๋ฒ„์—์„œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๊ทธ ๋ฐ์ดํ„ฐ๋Š” ์บ์‹œ๋˜๊ณ  ๊ทธ ์ดํ›„ ์š”์ฒญ๋ถ€ํ„ฐ๋Š” ๊ฐœ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ ์ผ์น˜ํ•˜๋Š” ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ์„œ๋ฒ„์— ์š”์ฒญํ•˜์ง€ ์•Š๊ณ  ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์š”์ฒญ์ด ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐœ์ƒํ•ด๋„, ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์–ด ์ค‘๋ณต ์š”์ฒญ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค. 
  • ๋ฐ์ดํ„ฐ์˜ ์‹ ์„ ๋„
    : TanStack Query๋Š” ์บ์‹œํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ ์„ (Fresh)ํ•˜๊ฑฐ๋‚˜ ์ƒํ•œ(Stale) ์ƒํƒœ๋กœ ๊ตฌ๋ถ„ํ•ด ๊ด€๋ฆฌํ•œ๋‹ค. ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ ์„ ํ•˜๋‹ค๋ฉด ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋งŒ์•ฝ ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒํ–ˆ๋‹ค๋ฉด ์„œ๋ฒ„์— ๋‹ค์‹œ ์š”์ฒญํ•ด ์‹ ์„ ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒํ•˜๋Š” ๋ฐ๊นŒ์ง€ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์€ staleTime ์˜ต์…˜์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‹ ์„ ํ•œ์ง€ ์ƒํ–ˆ๋Š”์ง€ ์—ฌ๋ถ€๋Š” isStale๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ํ˜„์žฌ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š” ํ”„๋กœ์ ํŠธ ๊ธฐ์ค€์œผ๋กœ MSW๋‚˜ ๋ฐฑ์—”๋“œ API์—์„œ ๋ฐ›์•„์˜ค๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ TanStack Query๋ฅผ ํ†ตํ•ด, ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ | ๋กœ๋”ฉ ์ƒํƒœ ๊ด€๋ฆฌ | ์—๋Ÿฌ ์ƒํƒœ ๊ด€๋ฆฌ | ์บ์‹ฑ | ๋‹ค์‹œ ์š”์ฒญํ•˜๊ธฐ ๋“ฑ์„ ๋Œ€์‹ ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 


 

 

 

 

2. ์ฝ”๋“œ ๋น„๊ต๋กœ ๋ณด๋Š” ํ•„์š”์„ฑ

  • TanStackQuery (X)
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
    fetch('/api/result')
        .then((res) => res.json())
        .then((data) => {
            setData(data);
            setIsLoading(false);
        })
        .catch((err) => {
            setError(err);
            setIsLoading(false);
        });
}, []);
์œ„์˜ ์ฝ”๋“œ๋ฅผ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•ด๋ณด๊ฒ ๋‹ค. data: null, isLoading: true, error: null ์ƒํƒœ๋กœ ํ™”๋ฉด์ด ๊ทธ๋ ค์ง„๋‹ค. ๊ทธ๋ฆฌ๊ณ  useEffect๊ฐ€ ์‹คํ–‰๋˜๋ฉด์„œ ๋ฐฑ์—”๋“œ์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•œ๋‹ค. ์ฒซ๋ฒˆ์งธ then์—์„œ ์‘๋‹ต์„ JSON ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋‘๋ฒˆ์งธ then์—์„œ ๋ณ€ํ™˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ setData์— ๋„ฃ๊ณ , setIsLoading(false)๋ฅผ ํ˜ธ์ถœํ•ด ๋กœ๋”ฉ์ด ๋๋‚ฌ์Œ์„ ์•Œ๋ฆฐ๋‹ค. catch๋กœ ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜๋‚˜ ์„œ๋ฒ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด error ์ƒํƒœ์— ์—๋Ÿฌ ๋‚ด์šฉ์„ ์ €์žฅํ•˜๊ณ  ๋กœ๋”ฉ์„ ์ข…๋ฃŒํ•œ๋‹ค.

ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ง€๋ฉด ์œ„์˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๋กœ๋”ฉ ์ƒํƒœ, ์—๋Ÿฌ ์ƒํƒœ๋ฅผ ๋งค๋ฒˆ ์ง์ ‘ ๋งŒ๋“ค์–ด์ค˜์•ผํ•œ๋‹ค. ๊ทธ๋ž˜์„œ TanStackQuery๋ฅผ ์จ์„œ ์œ„์˜ ์ฝ”๋“œ๋ฅผ ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

 

 

  • TanStackQuery (O)
const { data, isLoading, isError } = useQuery({
    queryKey: ['result'],
    queryFn: getResult,
});
data๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๊ฐ€ ๋œ๋‹ค. isLoading์„ ๋กœ๋”ฉ ์ค‘์ธ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. isError๋Š” ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค. querKey๋Š” ์ด ๋ฐ์ดํ„ฐ์˜ ์ด๋ฆ„ํ‘œ๊ณ  queryFn์€ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜๊ฐ€ ๋œ๋‹ค.

TanStackQuery๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋˜ ์ฝ”๋“œ๋Š” useState๋ฅผ 3๊ฐœ๋‚˜ ๋งŒ๋“ค๊ณ  fetch์˜ ์„ฑ๊ณต,์‹คํŒจ ์‹œ์ ๋งˆ๋‹ค ์ผ์ผ์ด setํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์ค˜์•ผ ํ–ˆ๋‹ค. TanStackQuery๋ฅผ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ๋Š” data, isLoading, isError ์ƒํƒœ๋ฅผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์•Œ์•„์„œ ๊ด€๋ฆฌํ•˜๊ณ  ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ๋˜์ ธ์ค€๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” ๊ทธ๋ƒฅ ๋ฐ›์•„์„œ ์“ฐ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

TanStackQuery๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋˜ ์ฝ”๋“œ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ผ๋Š” ์‹์˜ ๋ช…๋ นํ˜• ๋ฐฉ์‹์ด์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ TanStackQuery๋ฅผ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ๋Š” ์ด ๋ฐ์ดํ„ฐ๋Š” result๋ผ๋Š” ํ‚ค๋ฅผ ๊ฐ€์ง€๊ณ  getResult ํ•จ์ˆ˜๋กœ ๊ฐ€์ ธ์˜จ๋‹ค๊ณ  ์ •์˜๋งŒ ํ•ด๋‘๋Š” ์„ ์–ธ์  ๋ฐฉ์‹์ด๋‹ค. 

useQuery๋Š” ๋™์ผํ•œ ํ‚ค(result) ์š”์ฒญ ์‹œ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋œ ๊ฐ’์„ ์ฆ‰์‹œ ๋ฐ˜ํ™˜(์บ์‹ฑ)ํ•˜๊ณ  ๋„คํŠธ์›Œํฌ ์žฅ์•  ์‹œ ์ž๋™์œผ๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ์žฌ์‹œ๋„๋ฅผ ํ•œ๋‹ค. ๋ธŒ๋ผ์šฐ์ € ์ฐฝ์„ ๋‹ค์‹œ ํฌ์ปค์Šคํ•˜๊ฑฐ๋‚˜ ๋„คํŠธ์›Œํฌ๊ฐ€ ์žฌ์—ฐ๊ฒฐ๋  ๋•Œ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๊ณ  ๋™์ผํ•œ API ์š”์ฒญ์ด ๋™์‹œ์— ์—ฌ๋Ÿฌ ๊ฐœ ๋ฐœ์ƒํ•ด๋„ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•œ๋‹ค.

 

 

 

 


 

 

 

 

 

3. TanStackQuery ์„ค์น˜

  • TanStackQuery ์„ค์น˜ ๋ช…๋ น์–ด (npm์„ ์‚ฌ์šฉ ์ค‘์ธ ๊ฒฝ์šฐ)

ํ”„๋กœ์ ํŠธ ๋‚ด ํ„ฐ๋ฏธ๋„์— ์ ๋Š”๋‹ค

npm i @tanstack/react-query

 

 

  • React Query Devtools ์„ค์น˜ (ํ•„์ˆ˜ ์•„๋‹˜) 
npm i @tanstack/react-query-devtools

 

๊ฐœ๋ฐœ ์ค‘์— React Query๊ฐ€ ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜๊ณ  ์žˆ๋Š”์ง€ ๋ˆˆ์œผ๋กœ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด Devtools๋ฅผ ์„ค์น˜ํ•˜๋ฉด ๋œ๋‹ค. ์ด๋Š” ์„ ํƒ์‚ฌํ•ญ์ด๋‹ค!

 

 

 

 


 

 

 

 

4. TanStackQuery ์ฝ”๋“œ ์ž‘์„ฑ

  • QueryProvider.tsx ํŒŒ์ผ ๋งŒ๋“ค๊ธฐ

 

 

  • QueryProvider ์ „์ฒด ์ฝ”๋“œ
'use client';

import { ReactNode, useState } from 'react';
import {
    QueryClient,
    QueryClientProvider,
} from '@tanstack/react-query';

type QueryProviderProps = {
    children: ReactNode;
};

export default function QueryProvider({ children }: QueryProviderProps) {
    const [queryClient] = useState(
        () =>
            new QueryClient({
                defaultOptions: {
                    queries: {
                        staleTime: 1000 * 30,
                        retry: 1,
                        refetchOnWindowFocus: false,
                    },
                    mutations: {
                        retry: 0,
                    },
                },
            })
    );

    return (
        <QueryClientProvider client={queryClient}>
            {children}
        </QueryClientProvider>
    );
}
์•ฑ ์ „์ฒด์—์„œ React Query๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋„๋ก QueryClientProvider๋กœ ๊ฐ์‹ธ์•ผ ํ•œ๋‹ค. ๊ณต์‹ ๋ฌธ์„œ์— ๋ณด๋ฉด QueryClient๋ฅผ ๋งŒ๋“ค๊ณ , QueryClientProvider๋กœ ๊ฐ์‹ธ๋Š” ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•๋Šฅ๋กœ ์•ˆ๋‚ดํ•œ๋‹ค. ๊ธ€ ๋งจ ์•„๋ž˜์— ๋งํฌ๋ฅผ ์ฒจ๋ถ€ํ•˜๊ฒ ๋‹ค. ๊ทธ ์•ˆ์— ๋ณด๋ฉด ๊ฐ์ž ํ”„๋กœ์ ํŠธ์— ๋งž์ถฐ์„œ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ช…๋ น์–ด๋„ ์ž˜ ๋‚˜์™€์žˆ๋‹ค.

 

 

  • ์„ค๋ช…์ด ํฌํ•จ๋œ ์ฝ”๋“œ
'use client';

import { ReactNode, useState } from 'react';
import {
    QueryClient,
    QueryClientProvider,
} from '@tanstack/react-query';

type QueryProviderProps = {
    // ์ปดํฌ๋„ŒํŠธ์˜ ํ•˜์œ„ ์š”์†Œ๋“ค์„ ๋ฐ›๊ธฐ ์œ„ํ•œ ํƒ€์ž… ์ •์˜
    children: ReactNode;
};

export default function QueryProvider({ children }: QueryProviderProps) {

    // QueryClient ์ธ์Šคํ„ด์Šค๋ฅผ useState๋กœ ๊ด€๋ฆฌ
    // ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ํด๋ผ์ธํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์„ ๋ง‰์•„์คŒ
    // ์ดˆ๊ธฐ๊ฐ’ ์ƒ์„ฑ ํ•จ์ˆ˜ (() = new QueryClient)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์ƒ์„ฑ๋˜๋„๋ก ํ•จ
    const [queryClient] = useState(
        () =>
            new QueryClient({

                // ์ „์—ญ ๊ธฐ๋ณธ ์˜ต์…˜์„ ์„ค์ •
                defaultOptions: {
                    queries: {
                        // ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ ์„ ํ•˜๋‹ค๊ณ  ๊ฐ„์ฃผ๋˜๋Š” ์‹œ๊ฐ„ (30์ดˆ)
                        // ์ด ์‹œ๊ฐ„ ๋™์•ˆ์€ ๋™์ผํ•œ ์ฟผ๋ฆฌ๋ฅผ ์š”์ฒญํ•˜๋ฉด ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•จ
                        staleTime: 1000 * 30,
                        // ์š”์ฒญ ์‹คํŒจ ์‹œ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ํ•˜๋Š” ํšŸ์ˆ˜, ํ•œ๋ฒˆ๋งŒ ์‹œ๋„ํ•จ
                        retry: 1,
                        // ๋ธŒ๋ผ์šฐ์ € ํƒญ์„ ๋‹ค์‹œ ํด๋ฆญํ–ˆ์„ ๋•Œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ• ์ง€ ์ •ํ•˜๋Š”๋ฐ
                        // false๋กœ ์„ค์ •ํ•ด์„œ ๋„คํŠธ์›Œํฌ ๋น„์šฉ ์ค„์ž„
                        refetchOnWindowFocus: false,
                    },
                    // ๋ฐ์ดํ„ฐ ์ƒ์„ฑ, ์ˆ˜์ •, ์‚ญ์ œ ์š”์ฒญ ์‹œ ์˜ต์…˜์œผ๋กœ
                    // ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฐ”๋กœ ์•Œ๋ฆฌ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ ์žฌ์‹œ๋„๋ฅผ 0์œผ๋กœ ์„ค์ •
                    mutations: {
                        retry: 0,
                    },
                },
            })
    );

    // Provider๋กœ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ์„œ Queryclient๋ฅผ ๊ณต๊ธ‰
    return (
        <QueryClientProvider client={queryClient}>
            {children}
        </QueryClientProvider>
    );
}

 

 

  • layout.tsx
    return (
        <html lang="ko">
        <body className={`${geistSans.variable} ${geistMono.variable}`}>
        <MswProvider>
            <QueryProvider>
                {children}
            </QueryProvider>

            <MagicToaster />
        </MswProvider>
        </body>
        </html>
    );
}
{children}์ด QueryProvider์— ๊ฐ์‹ธ์ ธ ์žˆ๋‹ค. children์€ ๋ณดํ†ต ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŒ๋“  ํŽ˜์ด์ง€๋ฅผ ๋งํ•œ๋‹ค. ์ง€๊ธˆ ์ด ํ”„๋กœ์ ํŠธ์—์„  ๋ฉ”์ธํŽ˜์ด์ง€, ๋กœ๋”ฉํŽ˜์ด์ง€, ๊ฒฐ๊ณผ ํŽ˜์ด์ง€๊ฐ€ children์— ๋“ค์–ด๊ฐ„๋‹ค.

์ด children์„ QueryProvider๋กœ ๊ฐ์‹ธ์•ผ userQuery( ), useMutation( ), useQueryClient( )๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 


 

 

 

 

5. ๋ฉ”์ธํŽ˜์ด์ง€์— TanStackQuery ์ ์šฉํ•˜๊ธฐ

  • import
import { useMutation, useQuery } from '@tanstack/react-query';

 

  • ์„ ํƒ ์š”์†Œ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์š”์ฒญ
const {
    data: optionData,
    isLoading: isLoadingOptions,
    isError: isOptionError,
} = useQuery({
    queryKey: ['lotto-options'],
    queryFn: getLottoOptions,
});
ํ™”๋ฉด์— ํ–‰์šด, ์กฐ์ƒ๋‹˜์˜ ๋„์›€ ๋“ฑ ์‚ฌ์šฉ์ž๊ฐ€ ์š”์†Œ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ๊ฐ„์ด ์žˆ๋‹ค. ์ด๋Ÿฐ ์š”์†Œ๋“ค์„ useQuery๋กœ ๋ถˆ๋Ÿฌ์˜ค๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

queryKey๋Š” ๋ฐ์ดํ„ฐ์˜ ์ด๋ฆ„์œผ๋กœ ReactQuery๋Š” lotto-options๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค. ์•„๊นŒ ๋ฐ์ดํ„ฐ์˜ ์‹ ์„ ํ•˜๋‹ค๊ณ  ํŒ๋‹จํ•˜๋Š” ์‹œ๊ฐ„์„ 30์ดˆ๋กœ ์„ค์ •ํ–ˆ๋‹ค. 30์ดˆ ์•ˆ์— lotto-options ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด React Query๋Š” ์•„๊นŒ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ผ๊ณ  ํŒ๋‹จํ•˜๊ณ  ์บ์‹œ๊ฐ€ ์žˆ๋Š” ๊ฑธ ํ™•์ธํ•œ ๋’ค, ์žฌํ™œ์šฉํ•œ๋‹ค. 

queryFn์€ ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜๋‹ค. lotto-options ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ๋Š” getLottoOptions ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์ž‘์„ฑํ–ˆ๋‹ค.

useQuery๋Š” ๊ฒฐ๊ณผ๋กœ ์—ฌ๋Ÿฌ ์ƒํƒœ๋ฅผ ์ค€๋‹ค. ์„œ๋ฒ„์—์„œ ์ง์ ‘ ๋ฐ›์•„์˜ค๋Š” OptionData, ์„ ํƒ ์š”์†Œ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘์ธ์ง€ ์•Œ๋ ค์ฃผ๋Š” isLoadingOptions, ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋‹ค๊ฐ€ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋Š”์ง€ ์•Œ๋ ค์ฃผ๋Š” isOptionsError๊ฐ€ ์žˆ๋‹ค.

 

 

  • ๋กœ๋˜ ๋ฒˆํ˜ธ ์ƒ์„ฑ ์š”์ฒญ
const drawMutation = useMutation({
    mutationFn: drawLottoNumbers,

    onSuccess: (result) => {
        sessionStorage.setItem('lotto-result', JSON.stringify(result));
        router.push('/loading');
    },

    onError: (error) => {
        const message =
            error instanceof Error
                ? error.message
                : '๊ฒฐ๊ณผ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.';

        showMagicToast(message);
    },
});
useMutation์€ ์‹คํ–‰, ๋ณ€๊ฒฝ, ์ƒ์„ฑ ์š”์ฒญ์ด๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์š”์†Œ 3๊ฐœ๋ฅผ ์„ ํƒํ•˜๊ณ  ๋งŒ๋“ค๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์‹คํ–‰์ด ๋œ๋‹ค.

mutationFn์€ ์‹ค์ œ๋กœ ์‹คํ–‰ํ•  ํ•จ์ˆ˜๋กœ, drawLottoNumbers ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

onSuccess๋Š” ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ์‹คํ–‰๋˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. ๋งŒ์•ฝ ์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด ๋กœ๋˜ ๊ฒฐ๊ณผ๋ฅผ ๋ธŒ๋ผ์šฐ์ €์˜ sessionStorage์— ์ €์žฅํ•œ๋‹ค. ์ €์žฅํ•œ ๋’ค ๋ฐ”๋กœ ๋กœ๋”ฉํŽ˜์ด์ง€๋กœ ๋ณด๋‚ด๊ณ  ๊ฒฐ๊ณผํŽ˜์ด์ง€์— ์ €์žฅํ•œ ๊ฐ’์„ ๊บผ๋‚ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ฃผ๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค.

onError๋Š” ์š”์ฒญ์ด ์‹คํŒจํ•˜๋ฉด ์‹คํ–‰๋˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. ์„œ๋ฒ„ ์š”์ฒญ ์ค‘์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋ฉด ํ† ์ŠคํŠธ๋กœ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๊บผ๋‚ด์„œ ๋ณด์—ฌ์ค€๋‹ค.

 

 

  • ์‹ค์ œ ์‹คํ–‰๋˜๋„๋ก ํ•˜๋Š” ์ฝ”๋“œ
    const handleCreateClick = () => {
        if (selectedOptions.length !== 3) {
            showMagicToast('์š”์†Œ 3๊ฐ€์ง€๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.');
            return;
        }

        drawMutation.mutate({
            selectedOptions,
        });
    };
useMutation์ด ์‹ค์ œ๋กœ ์‹คํ–‰๋˜๋Š” ๋ถ€๋ถ„์ด๋‹ค. if๋ฌธ์„ ํ†ต๊ณผํ•˜๋ฉด drawlottoNumbers ํ•จ์ˆ˜์—๊ฒŒ selectedOptions(์„ ํƒํ•œ ์š”์†Œ)๋ฅผ ๋„˜๊ฒจ์„œ ์‹คํ–‰ํ•˜๋„๋ก ์ž‘์„ฑ๋œ ์ฝ”๋“œ์ด๋‹ค. ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜์ž๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ์š”์†Œ 3๊ฐœ๊ฐ€ API ํ•จ์ˆ˜๋กœ ์ „๋‹ฌ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ ํ–‰์šด, ๋‚ด๋ˆ, 1๋“ฑ์ดํ•„์š”ํ•ด๋ฅผ ์„ ํƒํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•œ๋‹ค๋ฉด, mutate๋กœ ๋„˜์–ด๊ฐ€๋Š” ๊ฐ’์€ selectedOptions: ['ํ–‰์šด', '๋‚ด๋™', '1๋“ฑ์ดํ•„์š”ํ•ด']๊ฐ€ ๋  ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด drawLottoNumbers ํ•จ์ˆ˜๊ฐ€ ์ด ๊ฐ’์„ ๋ฐ›์•„์„œ ๋กœ๋˜ ๋ฒˆํ˜ธ ์ƒ์„ฑ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.

 

 

  • ์ค‘๋ณต ๋ง‰๊ธฐ 
                    <button
                        type="button"
                        className={styles.createButton}
                        onClick={handleCreateClick}
                        disabled={drawMutation.isPending}
                    >
                        {drawMutation.isPending ? (
                            <>
                                ๊ฒฐ๊ณผ ์ƒ์„ฑ
                                <br />
                                ์ค‘...
                            </>
                        ) : (
                            <>
                                ์ฟต์ง์ฟต์ง
                                <br />
                                ๋งŒ๋“ค๊ธฐ
                            </>
                        )}
                    </button>
disabled={drawMutation.isPending}์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋‘์—ˆ๋‹ค. ํ‰์†Œ์—๋Š” ์ฟต์ง์ฟต์ง ๋งŒ๋“ค๊ธฐ๋ผ๋Š” ๋ฒ„ํŠผ์ด ๋ณด์ด๊ฒ ์ง€๋งŒ, ๋กœ๋˜ ๋ฒˆํ˜ธ ์ƒ์„ฑ ์ค‘์ด๋ฉด isPending(mutation์ด ์‹คํ–‰ ์ค‘์ธ์ง€ ์•Œ๋ ค์ฃผ๋Š” ์ƒํƒœ)์ด true๊ฐ€ ๋˜๋ฉด์„œ ๊ฒฐ๊ณผ ์ƒ์„ฑ ์ค‘์ด๋ผ๊ณ  ๋ฐ”๋€๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฒ„ํŠผ์„ ๋ˆŒ๋ฆฌ์ง€ ์•Š๊ฒŒ ๋œ๋‹ค. 

 

 

 


 

 

- TanStackQuery

 

Installation | TanStack Query React Docs

You can install React Query via , or a good ol' <script via . NPM bash npm i @tanstack/react-query or bash pnpm add @tanstack/react-query or bash yarn add @tanstack/react-query or bash bun add @tansta...

tanstack.com