provider를 따로 관리하고자 providers 폴더를 만들어주었다.
📁 FSD-project-setting/src/app/providers
pnpm add @tanstack/react-query
pnpm add @tanstack/react-query-devtools
📁 FSD-project-setting/src/app/providers/QueryClientProvider.tsx
import {
QueryClient,
Query,
QueryClientProvider as TanStackQueryClientProvider,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { AxiosError } from "axios";
type Props = {
children: React.ReactNode;
};
const queryClient = new QueryClient({
defaultOptions: {
queries: {
throwOnError: true,
},
},
});
export const QueryClientProvider = ({ children }: Props) => {
return (
<TanStackQueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools
buttonPosition="bottom-left"
errorTypes={[
{
name: "Error",
initializer: errorInitializer(new Error("Error message")),
},
{
name: "Axios Error",
initializer: errorInitializer(new AxiosError("Axios error")),
},
]}
/>
</TanStackQueryClientProvider>
);
};
function errorInitializer(error: Error) {
return (query: Query) => {
query.reset();
return error;
};
}
pnpm add react-error-boundary
📁 FSD-project-setting/src/app/providers/ErrorBoundaryProvider.tsx
import { ErrorFallback } from "@/shared/ui/fallback/ErrorFallback";
import { ErrorInfo } from "react";
import { ErrorBoundary } from "react-error-boundary";
type Props = {
children: React.ReactNode;
};
const handleError = (error: Error, info: ErrorInfo) => {
console.log("에러 발생:", error);
console.log("컴포넌트 스택:", info.componentStack);
};
export const ErrorBoundaryProvider = ({ children }: Props) => {
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onError={handleError}>
{children}
</ErrorBoundary>
);
};
📁 FSD-project-setting/src/shared/ui/fallback/ErrorFallback.tsx
import { FallbackProps } from "react-error-boundary";
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
return (
<div>
<h1>ErrorFallback</h1>
<h2>문제가 발생했습니다</h2>
<p>{error.message}</p>
<button onClick={resetErrorBoundary}>재시도</button>
</div>
);
};
해당 프로젝트는 lazy와 suspense를 적용할 때 lazy로 발생한 suspense의 로딩 ui를 모두 같게 할 예정이다. 따라서 Suspense를 Router의 element에 각각 적용하기 번거롭기 때문에 Provider로 빼서 한번에 적용해주었다.
📁 FSD-project-setting/src/app/providers/RouterProvider.tsx
import React, { Suspense } from "react";
type Props = {
children: React.ReactNode;
};
export const RouterProvider = ({ children }: Props) => (
<Suspense fallback="Loading...">{children}</Suspense>
);
나는 App.tsx에 여러 개를 적기보다는 Provider라는 태그로 한번에 적용하고 싶었다. 그래서 다음과 같이 Provider/index.tsx에 Provider를 한번에 모아서 적용하기로 했다.
import { ErrorBoundaryProvider } from "@/app/providers/ErrorBoundaryProvider";
import { QueryClientProvider } from "@/app/providers/QueryClientProvider";
import { RouterProvider } from "@/app/providers/RouterProvider";
type Props = {
children: React.ReactNode;
};
export const Providers = ({ children }: Props) => (
<QueryClientProvider>
<ErrorBoundaryProvider>
<RouterProvider>{children}</RouterProvider>
</ErrorBoundaryProvider>
</QueryClientProvider>
);
App.tsx에서 Providers가 모든 router들을 감싸주고 있다는 것을 명시적으로 알려주기 위해
RouterProvider를 Providers 폴더가 아니라 routers 폴더의 index.tsxroot(메인 라우터)에 RouterProvider를 따로 빼주었다.
📁 FSD-project-setting/src/app/routers/index.tsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const root = createBrowserRouter([
{
path: "/",
element: <div>Home Page</div>, // 기본 페이지 설정
},
]);
const AppRouters = () => {
return <RouterProvider router={root} />;
};
export default AppRouters;
결과
import { Providers } from "@/app/providers";
import AppRouters from "@/app/routers";
function App() {
return (
<Providers>
<AppRouters />
</Providers>
);
}
export default App;
pnpm add styled-reset styled-components
app 폴더 안에 넣을지 shared/styles 폴더 안에 넣을지 고민했지만, GlobalStyle은 여러 레이어에서 재사용 된다기보다는 앱의 전역 설정으로 간주된다는 표현이 더 알맞다고 생각되어 app 레이어에 두었다.
📁 FSD-project-setting/src/app/styles/global.style.ts
import { createGlobalStyle } from "styled-components";
import reset from "styled-reset";
const GlobalStyle = createGlobalStyle`
${reset}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
/* colors */
/* fonts */
}
`;
export default GlobalStyle;
📁 FSD-project-setting/src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "@/app/App";
import GlobalStyle from "@/app/styles/global.style";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<GlobalStyle />
<App />
</StrictMode>
);
pnpm add react-router-dom
FSD 공식 문서-React Query
[TanStack Query] QueryFactory로 API 관리하기