diff --git a/.eslintrc.cjs b/.eslintrc.cjs index da9fec22..ed1d7664 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -9,11 +9,18 @@ module.exports = { ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], + plugins: ['react-refresh', 'react-hooks'], rules: { 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true } ] - } + }, + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': [ + 'warn', + { + additionalHooks: 'useRecoilCallback' + } + ] }; diff --git a/package-lock.json b/package-lock.json index 26dc31fa..228a9216 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,18 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@tanstack/react-query": "^5.17.9", + "@tanstack/react-query-devtools": "^5.17.9", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.21.1" + "react-error-boundary": "^4.0.12", + "react-router-dom": "^6.21.1", + "recoil": "^0.7.7" }, "devDependencies": { "@swc/core": "^1.3.102", "@swc/jest": "^0.2.29", + "@tanstack/eslint-plugin-query": "^5.17.7", "@types/jest": "^29.5.11", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", @@ -2472,6 +2477,193 @@ "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", "dev": true }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.17.7", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.17.7.tgz", + "integrity": "sha512-RpKZXIuplRrUZLqqh+jTM1yJP8/Ck21FpaSB5uGyc9LY8LNwxC8AwgaRAXVOZzKVeQMunnt3HrK83HME+7jnGw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.62.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.17.9", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.9.tgz", + "integrity": "sha512-8xcvpWIPaRMDNLMvG9ugcUJMgFK316ZsqkPPbsI+TMZsb10N9jk0B6XgPk4/kgWC2ziHyWR7n7wUhxmD0pChQw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.17.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.17.7.tgz", + "integrity": "sha512-TfgvOqza5K7Sk6slxqkRIvXlEJoUoPSsGGwpuYSrpqgSwLSSvPPpZhq7hv7hcY5IvRoTNGoq6+MT01C/jILqoQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.17.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.17.9.tgz", + "integrity": "sha512-M5E9gwUq1Stby/pdlYjBlL24euIVuGbWKIFCbtnQxSdXI4PgzjTSdXdV3QE6fc+itF+TUvX/JPTKIwq8yuBXcg==", + "dependencies": { + "@tanstack/query-core": "5.17.9" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.17.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.17.9.tgz", + "integrity": "sha512-1viWP/jlO0LaeCdtTFqtF1k2RfM3KVpvwVffWv+PMNkS2u4s8YGUM17r3p82udbF9BY1mE7aHqQ3MM1errF5lQ==", + "dependencies": { + "@tanstack/query-devtools": "5.17.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.17.9", + "react": "^18.0.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4125,6 +4317,11 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hamt_plus": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", + "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6049,6 +6246,17 @@ "react": "^18.2.0" } }, + "node_modules/react-error-boundary": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.12.tgz", + "integrity": "sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6084,6 +6292,25 @@ "react-dom": ">=16.8" } }, + "node_modules/recoil": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", + "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", + "dependencies": { + "hamt_plus": "1.0.2" + }, + "peerDependencies": { + "react": ">=16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", @@ -6554,6 +6781,27 @@ } } }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index bb78fda5..f6bce8c9 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,18 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "@tanstack/react-query": "^5.17.9", + "@tanstack/react-query-devtools": "^5.17.9", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.21.1" + "react-error-boundary": "^4.0.12", + "react-router-dom": "^6.21.1", + "recoil": "^0.7.7" }, "devDependencies": { "@swc/core": "^1.3.102", "@swc/jest": "^0.2.29", + "@tanstack/eslint-plugin-query": "^5.17.7", "@types/jest": "^29.5.11", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", diff --git a/src/App.tsx b/src/App.tsx index 1e1181b4..5389b322 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,59 @@ -import GlobalStyle from '@styles/GlobalStyle'; -import { ThemeProvider } from '@emotion/react'; -import theme from '@styles/theme'; import { BrowserRouter } from 'react-router-dom'; +import { ThemeProvider } from '@emotion/react'; +import { + QueryClient, + QueryClientProvider, + useQueryErrorResetBoundary +} from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { RecoilRoot } from 'recoil'; +import { ErrorBoundary } from 'react-error-boundary'; +import { Suspense } from 'react'; + import { MainRouter } from './routes'; +import GlobalStyle from '@styles/GlobalStyle'; +import theme from '@styles/theme'; +import { ErrorApp } from '@components/ErrorFallback'; +import { LoadingApp } from '@components/Loading'; + +const queryClient = new QueryClient(); + +/* HACK: logError 논의 필요 + + const logError = (error: Error, info: { componentStack: string }) => { + // Do something with the error, e.g. log to an external API + }; + + */ const App = () => { + const { reset } = useQueryErrorResetBoundary(); + return ( - <> - - - - - - - + + + + + + }> + + + + + + + + + ); }; diff --git a/src/components/ErrorFallback/ErrorApp/index.tsx b/src/components/ErrorFallback/ErrorApp/index.tsx new file mode 100644 index 00000000..af782ea1 --- /dev/null +++ b/src/components/ErrorFallback/ErrorApp/index.tsx @@ -0,0 +1,13 @@ +import { FallbackProps } from 'react-error-boundary'; + +const ErrorApp = ({ error, resetErrorBoundary }: FallbackProps) => { + return ( +
+ App 전체 에러 발생 +
{error.message}
+ +
+ ); +}; + +export default ErrorApp; diff --git a/src/components/ErrorFallback/index.tsx b/src/components/ErrorFallback/index.tsx new file mode 100644 index 00000000..b673b5ba --- /dev/null +++ b/src/components/ErrorFallback/index.tsx @@ -0,0 +1 @@ +export { default as ErrorApp } from './ErrorApp'; diff --git a/src/components/Loading/LoadingApp/index.tsx b/src/components/Loading/LoadingApp/index.tsx new file mode 100644 index 00000000..28a38a3f --- /dev/null +++ b/src/components/Loading/LoadingApp/index.tsx @@ -0,0 +1,5 @@ +const LoadingApp = () => { + return

로딩중...

; +}; + +export default LoadingApp; diff --git a/src/components/Loading/index.tsx b/src/components/Loading/index.tsx new file mode 100644 index 00000000..6cdde82e --- /dev/null +++ b/src/components/Loading/index.tsx @@ -0,0 +1 @@ +export { default as LoadingApp } from './LoadingApp'; diff --git a/src/components/index.tsx b/src/components/index.tsx deleted file mode 100644 index 567e60aa..00000000 --- a/src/components/index.tsx +++ /dev/null @@ -1 +0,0 @@ -// export { default as ExampleFoo } from './Login/LiginInputs';