Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
38 changes: 38 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/app/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -10,6 +11,8 @@ if (!rootEl) throw new Error('Root element "#root" not found');

createRoot(rootEl).render(
<StrictMode>
<App />
<AppProviders>
<App />
</AppProviders>
</StrictMode>
);
7 changes: 7 additions & 0 deletions src/app/providers/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { PropsWithChildren } from "react";

import QueryProvider from "./query-provider";

Check warning on line 3 in src/app/providers/index.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

`./query-provider` import should occur before type import of `react`

export default function AppProviders({ children }: PropsWithChildren) {

Check warning on line 5 in src/app/providers/index.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function component is not an arrow function
return <QueryProvider>{children}</QueryProvider>;
}
Comment on lines +5 to +7
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 query-provider로 바로 App.tsx를 감싸는 구조로 생각했는데, 여러 provider를 합성하는 역할을 수행하는 AppProviders를 별도로 둔 점이 인상깊었어요.
만약 다른 provider가 늘어날 경우, App.tsx가 여러 provider로 감싸지는 약간은 지저분한 구조가 되었을 것 같은데
앱 진입점을 깔끔하게 유지할 수 있는 방향이라 확장성 측면에서 너무 좋은 구조화같습니다❣️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

index.ts에서 단순히 export만 모아주는 방식이 아닌 AppProviders로 묶어주는 구조를 처음 봐서 조금 찾아보았는데 App.tsx 이 깔끔해지고 추가되는 프로바이더를 해당 파일에서만 추가하면 된다는 점에서 유지보수성이 굉장히 좋은 코드인 거 같습니다~ 하나 배워가욥~!!

26 changes: 26 additions & 0 deletions src/app/providers/query-provider.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comfit은 컴포넌트 명칭은 파스칼케이스로 정의하지만, 파일이나 폴더의 이름은 케밥케이스을 사용하고 있죠!
QueryProvider.ts그리고 queryClient.ts를 query-provider.tsquery-client.ts로 컨벤션 맞춰주면 좋을 것 같아요!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아우~꼼꼼해😍

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { PropsWithChildren } from "react";

Check warning on line 1 in src/app/providers/query-provider.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

`react` type import should occur after import of `@/shared/api`

Check warning on line 1 in src/app/providers/query-provider.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

There should be at least one empty line between import groups
import { Suspense, lazy } from "react";

Check warning on line 2 in src/app/providers/query-provider.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

`react` import should occur after import of `@tanstack/react-query`
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;
Comment on lines +7 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단순히 DEV && <ReactQueryDevtools />로 처리하다보면 ReactQueryDevtool 코드가 프로덕션 번들에 포함되었을텐데, lazy를 사용해 번들 사이즈를 줄인 부분 너무 좋습니다!!


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

Check warning on line 14 in src/app/providers/query-provider.tsx

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function component is not an arrow function
return (
<QueryClientProvider client={queryClient}>
{children}

{import.meta.env.DEV && ReactQueryDevtools ? (
<Suspense fallback={null}>
<ReactQueryDevtools initialIsOpen={false} />
</Suspense>
) : null}
</QueryClientProvider>
);
}
1 change: 1 addition & 0 deletions src/shared/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { queryClient } from "./query-client";
36 changes: 36 additions & 0 deletions src/shared/api/query-client.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

작성해주신 구조가 query-client인스턴스와 인스턴스를 제공하는 provider를 각각 sharedproviders 레이어로 분리한 점이 FSD구조 흐름과 잘 맞는다고 생각합니다!
한 가지 제안 드리고 싶은 점은 query-client.ts 파일의 위치인데요. query-client는 서버 상태, api 데이터에 대한 관리를 처리하는 역할을 담당하다보니 기존의 유틸 함수를 모아놓는 shared/lib보다 아래 구조처럼 axios-instance와 같이 API 관련 설정들이 모여있는 shared/api 내부에 두는 것이 더 직관적일 것 같다는 생각이 들었습니다!

📂 src
  └─📂 shared
      └─📂 api
          └─📄 query-client.ts

한 번 고려해주시면 좋을 부분 같아요!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 수빈님의 의견에 동의합니다! 유틸함수가 아닌 것은 아니지만 api를 위한 파일이라는 명확한 목적이 있기 때문에 api 폴더에 넣어두면 충분할 거 같아요 ☺️

Original file line number Diff line number Diff line change
@@ -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 후 공통 에러 핸들러 연결
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO로 추후에 필요한 작업 정리해둔 점 아주 굿~

// handleApiError(error);
console.error(error);
},
}),

mutationCache: new MutationCache({
onError: (error: unknown) => {
// TODO: API 초기 세팅 PR merge 후 공통 에러 핸들러 연결
// handleApiError(error);
console.error(error);
},
}),
});