Skip to content

Commit

Permalink
merge: 관리되지 않은 API 에러 분기 처리 개선 (UnhandledError) #407
Browse files Browse the repository at this point in the history
[REFACTOR] 관리되지 않은 API 에러 분기 처리 개선 (UnhandledError)
  • Loading branch information
rbgksqkr authored Dec 3, 2024
2 parents f39408f + 1304afe commit 7572b19
Show file tree
Hide file tree
Showing 14 changed files with 51 additions and 30 deletions.
8 changes: 6 additions & 2 deletions frontend/src/apis/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CustomError, NetworkError } from '@/utils/error';
import { CustomError, NetworkError, UnhandledError } from '@/utils/error';

interface RequestProps {
url: string;
Expand Down Expand Up @@ -26,11 +26,15 @@ const fetcher = {

return response;
} catch (error) {
if (!navigator.onLine) {
throw new NetworkError();
}

if (error instanceof CustomError) {
throw error;
}

throw new NetworkError();
throw new UnhandledError();
}
},

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/InviteModal/InviteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ const InviteModal = ({ isOpen, onClose, returnFocusRef }: InviteModalProps) => {
const inviteUrl = INVITE_URL(roomUuid);

const { copyToClipboard } = useClipBoard();
const { show } = useToast();
const { showToast } = useToast();

const handleCopy = () => {
copyToClipboard(inviteUrl);
show('링크가 복사되었습니다!');
showToast('링크가 복사되었습니다!');
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import DeferredComponent from '../DeferredComponent/DeferredComponent';
import AsyncErrorFallback from '../ErrorFallback/AsyncErrorFallback/AsyncErrorFallback';
import SpinnerErrorFallback from '../ErrorFallback/SpinnerErrorFallback/SpinnerErrorFallback';

import { CustomError } from '@/utils/error';
import { CustomError, UnhandledError } from '@/utils/error';

interface AsyncErrorBoundaryProps {
pendingFallback?: React.ReactNode;
Expand All @@ -25,7 +25,7 @@ const AsyncErrorBoundary = ({
<AsyncErrorFallback error={error} resetError={resetError} />
)}
onError={(error) => {
if (error instanceof CustomError) {
if (error instanceof CustomError || error instanceof UnhandledError) {
withScope((scope) => {
scope.setLevel('warning');
scope.setTag('api', 'internalServerError');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '../ErrorFallback.styled';

import ErrorDdangkong from '@/assets/images/errorDdangkong.webp';
import { CustomError } from '@/utils/error';
import { CustomError, UnhandledError } from '@/utils/error';

interface AsyncErrorFallback {
error: unknown;
Expand All @@ -22,7 +22,9 @@ const AsyncErrorFallback = ({ error, resetError }: AsyncErrorFallback) => {
return (
<section css={errorFallbackLayout}>
<img src={ErrorDdangkong} alt="에러나서 슬픈 땅콩" css={errorImage} />
<h2 css={errorText}>{error instanceof CustomError && error.message}</h2>
<h2 css={errorText}>
{(error instanceof CustomError || error instanceof UnhandledError) && error.message}
</h2>
<div css={fallbackButtonContainer}>
<Button onClick={resetError} text="다시 시도" size="medium" radius="medium" />
<Button onClick={goToHome} text="홈으로" size="medium" radius="medium" />
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/layout/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const TitleHeader = ({ title }: HeaderProps) => (

// 3. 가운데 제목, 우측 상단 차지하는 헤더 : 게임 대기 화면
export const RoomSettingHeader = ({ title }: HeaderProps) => {
const { show } = useModal();
const { showModal } = useModal();
const {
member: { isMaster },
} = useGetUserInfo();
Expand All @@ -75,11 +75,11 @@ export const RoomSettingHeader = ({ title }: HeaderProps) => {
const focusRef = useFocus<HTMLElement>();

const handleClickRoomSetting = () => {
show(RoomSettingModal, { returnFocusRef });
showModal(RoomSettingModal, { returnFocusRef });
};

const handleClickExit = () => {
show(AlertModal, { message: '정말로 나가시겠습니까?', onConfirm: handleExit });
showModal(AlertModal, { message: '정말로 나가시겠습니까?', onConfirm: handleExit });
};

return (
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/constants/errorStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const SERVER_ERROR_STATUS = 500;
export const NETWORK_ERROR_STATUS = 5001;
export const UNHANDLED_ERROR_STATUS = 5002;
6 changes: 3 additions & 3 deletions frontend/src/hooks/useDefaultMutationErrorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import useToast from '@/hooks/useToast';
import { CustomError, NetworkError } from '@/utils/error';

const useDefaultMutationErrorHandler = () => {
const { show } = useToast();
const { show: showModal } = useModal();
const { showToast } = useToast();
const { showModal } = useModal();

return (error: unknown) => {
if (error instanceof NetworkError) {
show(error.message);
showToast(error.message);
return;
}
if (error instanceof CustomError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import useModal from '@/hooks/useModal';

const ReadyMembersContainer = () => {
const { members, master } = useGetRoomInfo();
const { show } = useModal();
const { showModal } = useModal();
const queryClient = useQueryClient();
const returnFocusRef = useRef<HTMLButtonElement>(null);
const memberCountMessage = `총 인원 ${members.length}명`;

const handleClickInvite = () => {
show(InviteModal, { returnFocusRef });
showModal(InviteModal, { returnFocusRef });
};

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const RoomSetting = () => {
const {
member: { isMaster },
} = useGetUserInfo();
const { show } = useModal();
const { showModal } = useModal();

const screenReaderRoomSetting = `
방 정보.
Expand All @@ -30,7 +30,7 @@ const RoomSetting = () => {
제한시간 ${roomSetting.timeLimit / 1000}초.`;

const handleClickCategory = () => {
show(RoomSettingModal, { returnFocusRef });
showModal(RoomSettingModal, { returnFocusRef });
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ const NextRoundButton = () => {
const {
member: { isMaster },
} = useGetUserInfo();
const { show } = useModal();
const { showModal } = useModal();

const returnFocusRef = useRef<HTMLButtonElement>(null);

const randomRoundNextMessage = createRandomNextRoundMessage();
const isLastRound = balanceContent?.currentRound === balanceContent?.totalRound;

const showModal = () => {
show(AlertModal, { message: randomRoundNextMessage, onConfirm: moveNextRound, returnFocusRef });
const handleNextRoundModal = () => {
showModal(AlertModal, {
message: randomRoundNextMessage,
onConfirm: moveNextRound,
returnFocusRef,
});
};

return (
Expand All @@ -35,7 +39,7 @@ const NextRoundButton = () => {
ref={returnFocusRef}
style={{ width: '100%' }}
text={getNextRoundButtonText(isMaster, isLastRound, isPending || isSuccess)}
onClick={isLastRound ? moveNextRound : showModal}
onClick={isLastRound ? moveNextRound : handleNextRoundModal}
disabled={!isMaster || isPending || isSuccess}
/>
</div>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/providers/ModalProvider/ModalProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface Modal extends ModalProps {
}

interface ModalDispatchContextProps {
show: (Component: React.FC<ModalState> | null, props?: ModalProps) => void;
showModal: (Component: React.FC<ModalState> | null, props?: ModalProps) => void;
close: () => void;
}

Expand All @@ -33,7 +33,7 @@ const ModalProvider = ({ children }: PropsWithChildren) => {
onConfirm: () => {},
});

const show = (Component: React.FC<ModalState> | null, props?: ModalProps) => {
const showModal = (Component: React.FC<ModalState> | null, props?: ModalProps) => {
setModal({
Component,
title: props?.title,
Expand All @@ -52,7 +52,7 @@ const ModalProvider = ({ children }: PropsWithChildren) => {
}));
};

const dispatch = useMemo(() => ({ show, close }), []);
const dispatch = useMemo(() => ({ showModal, close }), []);

return (
<ModalDispatchContext.Provider value={dispatch}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useQueryClient } from '@tanstack/react-query';
import { PropsWithChildren } from 'react';

import { NETWORK_ERROR_STATUS, SERVER_ERROR_STATUS } from '@/constants/errorStatus';
import useDefaultMutationErrorHandler from '@/hooks/useDefaultMutationErrorHandler';
import { CustomError } from '@/utils/error';

const isServerError = (status: number) => status >= 500 && status !== 555;
const isServerError = (status: number) =>
status >= SERVER_ERROR_STATUS && status !== NETWORK_ERROR_STATUS;

// QueryClient는 모든 Provider에 공유되면서 공통 에러 핸들링 로직에 Toast와 Modal을 넣기 위해 setDefaultOptions 사용
// 테스트 환경에서 retry 값이 있을 경우 에러 폴백 테스트가 돌지 않아 분기 처리
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/providers/ToastProvider/ToastProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createPortal } from 'react-dom';
import { toastLayout } from './ToastProvider.styled';

interface ToastContext {
show: (message: string) => void;
showToast: (message: string) => void;
}

export const ToastContext = createContext<ToastContext | null>(null);
Expand All @@ -13,7 +13,7 @@ const ToastProvider = ({ children }: PropsWithChildren) => {
const [toastMessage, setToastMessage] = useState('');
const timerRef = useRef<NodeJS.Timeout | null>(null);

const show = useCallback((message: string) => {
const showToast = useCallback((message: string) => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
Expand All @@ -34,7 +34,7 @@ const ToastProvider = ({ children }: PropsWithChildren) => {
}, []);

return (
<ToastContext.Provider value={{ show }}>
<ToastContext.Provider value={{ showToast }}>
{children}
{toastMessage &&
createPortal(
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NETWORK_ERROR_STATUS, UNHANDLED_ERROR_STATUS } from '@/constants/errorStatus';
import { ERROR_MESSAGE } from '@/constants/message';
import { ErrorCode } from '@/types/error';

Expand All @@ -20,6 +21,11 @@ export class CustomError extends Error {
}

export class NetworkError extends Error {
status = 555;
status = NETWORK_ERROR_STATUS;
message = '네트워크가 불안정해요. 다시 시도해주세요!';
}

export class UnhandledError extends Error {
status = UNHANDLED_ERROR_STATUS;
message = '예기치 못한 에러가 발생했어요. 관리자에게 문의 바랍니다.';
}

0 comments on commit 7572b19

Please sign in to comment.