diff --git a/package.json b/package.json index 766fded..10b1682 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ ] }, "dependencies": { + "@tanstack/react-query": "^5.90.16", "@vanilla-extract/css": "^1.18.0", "@vanilla-extract/vite-plugin": "^5.1.4", "eslint-import-resolver-typescript": "^4.4.4", @@ -36,6 +37,7 @@ "@storybook/addon-docs": "^10.1.11", "@storybook/react-vite": "^10.1.11", "@svgr/plugin-svgo": "^8.1.0", + "@tanstack/react-query-devtools": "^5.91.2", "@types/node": "^24.10.1", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", @@ -55,6 +57,7 @@ "lint-staged": "^16.2.7", "storybook": "^10.1.11", "prettier": "3.7.4", + "storybook": "^10.1.11", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", "vite": "^7.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6856129..c9bdaaf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@tanstack/react-query': + specifier: ^5.90.16 + version: 5.90.16(react@19.2.3) '@vanilla-extract/css': specifier: ^1.18.0 version: 1.18.0 @@ -45,6 +48,9 @@ importers: '@svgr/plugin-svgo': specifier: ^8.1.0 version: 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3) + '@tanstack/react-query-devtools': + specifier: ^5.91.2 + version: 5.91.2(@tanstack/react-query@5.90.16(react@19.2.3))(react@19.2.3) '@types/node': specifier: ^24.10.1 version: 24.10.4 @@ -831,6 +837,23 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} + '@tanstack/query-core@5.90.16': + resolution: {integrity: sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==} + + '@tanstack/query-devtools@5.92.0': + resolution: {integrity: sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==} + + '@tanstack/react-query-devtools@5.91.2': + resolution: {integrity: sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg==} + peerDependencies: + '@tanstack/react-query': ^5.90.14 + react: ^18 || ^19 + + '@tanstack/react-query@5.90.16': + resolution: {integrity: sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==} + peerDependencies: + react: ^18 || ^19 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -3494,6 +3517,21 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@tanstack/query-core@5.90.16': {} + + '@tanstack/query-devtools@5.92.0': {} + + '@tanstack/react-query-devtools@5.91.2(@tanstack/react-query@5.90.16(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/query-devtools': 5.92.0 + '@tanstack/react-query': 5.90.16(react@19.2.3) + react: 19.2.3 + + '@tanstack/react-query@5.90.16(react@19.2.3)': + dependencies: + '@tanstack/query-core': 5.90.16 + react: 19.2.3 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 diff --git a/src/app/main.tsx b/src/app/main.tsx index 6af872b..aa57223 100644 --- a/src/app/main.tsx +++ b/src/app/main.tsx @@ -2,6 +2,7 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import App from "@app/App"; +import AppProviders from "@app/providers"; import "@app/styles/global.css"; @@ -10,6 +11,8 @@ if (!rootEl) throw new Error('Root element "#root" not found'); createRoot(rootEl).render( - + + + ); diff --git a/src/app/providers/index.tsx b/src/app/providers/index.tsx new file mode 100644 index 0000000..0ff33e1 --- /dev/null +++ b/src/app/providers/index.tsx @@ -0,0 +1,7 @@ +import type { PropsWithChildren } from "react"; + +import QueryProvider from "./query-provider"; + +export default function AppProviders({ children }: PropsWithChildren) { + return {children}; +} diff --git a/src/app/providers/query-provider.tsx b/src/app/providers/query-provider.tsx new file mode 100644 index 0000000..20b077c --- /dev/null +++ b/src/app/providers/query-provider.tsx @@ -0,0 +1,26 @@ +import type { PropsWithChildren } from "react"; +import { Suspense, lazy } from "react"; +import { QueryClientProvider } from "@tanstack/react-query"; + +import { queryClient } from "@/shared/api"; + +const ReactQueryDevtools = import.meta.env.DEV + ? lazy(async () => { + const mod = await import("@tanstack/react-query-devtools"); + return { default: mod.ReactQueryDevtools }; + }) + : null; + +export default function QueryProvider({ children }: PropsWithChildren) { + return ( + + {children} + + {import.meta.env.DEV && ReactQueryDevtools ? ( + + + + ) : null} + + ); +} diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts new file mode 100644 index 0000000..4a7c883 --- /dev/null +++ b/src/shared/api/index.ts @@ -0,0 +1 @@ +export { queryClient } from "./query-client"; \ No newline at end of file diff --git a/src/shared/api/query-client.ts b/src/shared/api/query-client.ts new file mode 100644 index 0000000..f889618 --- /dev/null +++ b/src/shared/api/query-client.ts @@ -0,0 +1,36 @@ +import { MutationCache, QueryCache, QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 0, + refetchOnWindowFocus: false, + refetchOnReconnect: true, + + staleTime: 30 * 1000, + gcTime: 10 * 60 * 1000, + + throwOnError: false, + }, + mutations: { + retry: 0, + throwOnError: false, + }, + }, + + queryCache: new QueryCache({ + onError: (error: unknown) => { + // TODO: API 초기 세팅 PR merge 후 공통 에러 핸들러 연결 + // handleApiError(error); + console.error(error); + }, + }), + + mutationCache: new MutationCache({ + onError: (error: unknown) => { + // TODO: API 초기 세팅 PR merge 후 공통 에러 핸들러 연결 + // handleApiError(error); + console.error(error); + }, + }), +});