diff --git a/src/app/App.tsx b/src/app/App.tsx index a76a0c6..cad867e 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -2,7 +2,9 @@ import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { RouterProvider } from 'react-router-dom'; -import { queryClient } from './providers/query-client'; +import { queryClient } from '@/shared/react-query'; +import { NetworkErrorToast } from '@/shared/ui'; + import { router } from './routers/index'; import './styles/global.scss'; @@ -10,6 +12,8 @@ function App() { return ( + + ); diff --git a/src/app/providers/query-client.ts b/src/app/providers/query-client.ts deleted file mode 100644 index 1c134b9..0000000 --- a/src/app/providers/query-client.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { QueryClient, QueryClientConfig } from '@tanstack/react-query'; - -const queryClientOptions: QueryClientConfig = { - defaultOptions: { - queries: { - staleTime: 10 * 60 * 1000, // 10 minutes - gcTime: 15 * 60 * 1000, // 15 minutes - refetchOnWindowFocus: false, - retry: false, - }, - }, -}; -export const queryClient = new QueryClient(queryClientOptions); diff --git a/src/app/styles/_reset.scss b/src/app/styles/_reset.scss index 8063ff5..a3a0a85 100644 --- a/src/app/styles/_reset.scss +++ b/src/app/styles/_reset.scss @@ -126,6 +126,10 @@ button { background-color: transparent; border: none; padding: 0; + + &:disabled { + color: inherit; + } } .icon { diff --git a/src/features/feed-main-like/api/useLikes.tsx b/src/features/feed-main-like/api/useLikes.tsx index f64894f..3d46a4f 100644 --- a/src/features/feed-main-like/api/useLikes.tsx +++ b/src/features/feed-main-like/api/useLikes.tsx @@ -1,7 +1,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { requestLikeFeed, requestUnlikeFeed } from '@/shared/axios'; -import { QUERY_KEYS } from '@/shared/consts'; +import { QUERY_KEYS } from '@/shared/react-query'; import { isErrorResponse } from '@/shared/utils'; import { FeedsQueryData } from '../consts'; diff --git a/src/shared/consts/index.ts b/src/shared/consts/index.ts index 783d4c4..58cd54c 100644 --- a/src/shared/consts/index.ts +++ b/src/shared/consts/index.ts @@ -1,3 +1,2 @@ -export { QUERY_KEYS } from './query-key/queryKeys'; export type * from './types'; export * from './color'; diff --git a/src/shared/react-query/consts/client.ts b/src/shared/react-query/consts/client.ts new file mode 100644 index 0000000..a2e6b12 --- /dev/null +++ b/src/shared/react-query/consts/client.ts @@ -0,0 +1,31 @@ +import { + MutationCache, + QueryCache, + QueryClient, + QueryClientConfig, +} from '@tanstack/react-query'; + +import { + handleMutationError, + handleQueryError, + handleQuerySuccess, +} from '../dir'; + +const queryClientOptions: QueryClientConfig = { + defaultOptions: { + queries: { + staleTime: 10 * 60 * 1000, // 10 minutes + gcTime: 15 * 60 * 1000, // 15 minutes + refetchOnWindowFocus: false, + retry: false, + }, + }, + queryCache: new QueryCache({ + onSuccess: () => handleQuerySuccess(), + onError: (_, query) => handleQueryError(query), + }), + mutationCache: new MutationCache({ + onError: () => handleMutationError(), + }), +}; +export const queryClient = new QueryClient(queryClientOptions); diff --git a/src/shared/react-query/consts/index.ts b/src/shared/react-query/consts/index.ts new file mode 100644 index 0000000..94adb01 --- /dev/null +++ b/src/shared/react-query/consts/index.ts @@ -0,0 +1,2 @@ +export { queryClient } from './client'; +export { QUERY_KEYS } from './keys'; diff --git a/src/shared/consts/query-key/queryKeys.ts b/src/shared/react-query/consts/keys.ts similarity index 100% rename from src/shared/consts/query-key/queryKeys.ts rename to src/shared/react-query/consts/keys.ts diff --git a/src/shared/react-query/dir/handleQuery.ts b/src/shared/react-query/dir/handleQuery.ts new file mode 100644 index 0000000..505b0f4 --- /dev/null +++ b/src/shared/react-query/dir/handleQuery.ts @@ -0,0 +1,31 @@ +import { Query, QueryKey } from '@tanstack/react-query'; + +import { removeErrorHandler, showErrorHandler } from './toastHandlers'; + +/** + * 쿼리 성공 핸들러 + */ +export function handleQuerySuccess() { + removeErrorHandler(); +} + +/** + * 쿼리 에러 핸들러 함수 + * @param query 쿼리 + * @if 피드 메인 페이지 2 페이지부터 에러가 발생하면 네트워크 에러 토스트를 띄웁니다. + */ +export function handleQueryError( + query: Query, +) { + const { queryKey, state } = query; + + // feeds 쿼리에서 2 페이지부터 에러가 발생하면 네트워크 에러 토스트를 띄웁니다. + if (queryKey[0] === 'feeds' && state.data) showErrorHandler(); +} + +/** + * 뮤테이션 에러 핸들러 + */ +export function handleMutationError() { + return; +} diff --git a/src/shared/react-query/dir/index.ts b/src/shared/react-query/dir/index.ts new file mode 100644 index 0000000..e591523 --- /dev/null +++ b/src/shared/react-query/dir/index.ts @@ -0,0 +1,2 @@ +export * from './handleQuery'; +export * from './toastHandlers'; diff --git a/src/shared/react-query/dir/toastHandlers.ts b/src/shared/react-query/dir/toastHandlers.ts new file mode 100644 index 0000000..a682bf7 --- /dev/null +++ b/src/shared/react-query/dir/toastHandlers.ts @@ -0,0 +1,27 @@ +import { Bounce, toast } from 'react-toastify'; + +const id = 'react-query-toast'; + +/** + * 에러 메시지를 토스트 메시지로 변환합니다. + */ +export function showErrorHandler() { + // reference: https://fkhadra.github.io/react-toastify/api/toast + if (!toast.isActive(id)) { + toast('인터넷 연결이 불안정해요', { + toastId: id, + autoClose: 3000, + position: 'bottom-center', + transition: Bounce, + }); + } +} + +/** + * 에러 메시지 토스트를 제거합니다. + */ +export function removeErrorHandler() { + if (toast.isActive(id)) { + toast.dismiss(id); + } +} diff --git a/src/shared/react-query/index.ts b/src/shared/react-query/index.ts new file mode 100644 index 0000000..208236d --- /dev/null +++ b/src/shared/react-query/index.ts @@ -0,0 +1 @@ +export * from './consts'; diff --git a/src/shared/ui/network-error-toast/NetworkErrorToast.scss b/src/shared/ui/network-error-toast/NetworkErrorToast.scss index b22760c..9060361 100644 --- a/src/shared/ui/network-error-toast/NetworkErrorToast.scss +++ b/src/shared/ui/network-error-toast/NetworkErrorToast.scss @@ -29,7 +29,7 @@ .Toastify__close-button, .Toastify__progress-bar--wrp { - display: none; + visibility: hidden; } } } diff --git a/src/shared/ui/network-error-toast/NetworkErrorToast.tsx b/src/shared/ui/network-error-toast/NetworkErrorToast.tsx index 9c29008..d902788 100644 --- a/src/shared/ui/network-error-toast/NetworkErrorToast.tsx +++ b/src/shared/ui/network-error-toast/NetworkErrorToast.tsx @@ -1,47 +1,17 @@ -import { useEffect, useRef } from 'react'; -import { ToastContainer, toast } from 'react-toastify'; +import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import './NetworkErrorToast.scss'; import { Icon } from '..'; -interface NetworkToastErrorProps { - isVisible: boolean; - errorMessage: string; -} - -export const NetworkErrorToast: React.FC = ({ - isVisible, - errorMessage, -}) => { - const toastId = useRef(-1); - - useEffect(() => { - // 에러 메시지가 보여야 하고, 현재 토스트가 표시되지 않았다면, - // 새로운 토스트를 표시하고 그 ID를 toastId에 저장합니다. - if (isVisible && toastId.current === -1) { - toastId.current = toast(errorMessage); - return; - } - - // 에러 메시지가 보여지지 않아야 하고, 현재 토스트가 표시되어 있다면, - // 토스트를 제거하고 toastId를 초기화합니다. - if (!isVisible && toastId.current !== -1) { - toast.dismiss(toastId.current); - toastId.current = -1; - return; - } - }, [isVisible, errorMessage]); - +export const NetworkErrorToast = () => { return ( } - hideProgressBar={true} - rtl={false} limit={1} + pauseOnHover={false} + pauseOnFocusLoss={false} theme='colored' /> ); diff --git a/src/widgets/feed-main-list/api/useInfinityFeeds.tsx b/src/widgets/feed-main-list/api/useInfinityFeeds.tsx index ba997fd..e3d080e 100644 --- a/src/widgets/feed-main-list/api/useInfinityFeeds.tsx +++ b/src/widgets/feed-main-list/api/useInfinityFeeds.tsx @@ -1,7 +1,8 @@ import { useInfiniteQuery } from '@tanstack/react-query'; import { axiosInstance } from '@/shared/axios'; -import { FetchFeeds, QUERY_KEYS } from '@/shared/consts'; +import { FetchFeeds } from '@/shared/consts'; +import { QUERY_KEYS } from '@/shared/react-query'; async function fetchFeeds( page: number, diff --git a/src/widgets/feed-main-list/ui/FeedMainList.tsx b/src/widgets/feed-main-list/ui/FeedMainList.tsx index 90be68d..0f94eca 100644 --- a/src/widgets/feed-main-list/ui/FeedMainList.tsx +++ b/src/widgets/feed-main-list/ui/FeedMainList.tsx @@ -1,4 +1,4 @@ -import { NetworkError, NetworkErrorToast, Observer } from '@/shared/ui'; +import { NetworkError, Observer } from '@/shared/ui'; import { useInfinityFeeds } from '../api/useInfinityFeeds'; @@ -42,10 +42,6 @@ export const FeedMainList = () => { )} {hasNextFeeds && } - ); };