Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 에러 핸들링 구현 #121

Merged
merged 11 commits into from
Aug 9, 2024
Merged

[Feat] 에러 핸들링 구현 #121

merged 11 commits into from
Aug 9, 2024

Conversation

jhj2713
Copy link
Member

@jhj2713 jhj2713 commented Aug 8, 2024

🖥️ Preview

404 not found

스크린샷 2024-08-08 오후 5 24 36

react-router-dom loader 요청 오류

-loader.mov

token 발급 오류

auth-token.mov

apply count 요청 오류

get-apply.mov

캐스퍼 봇 생성 요청 오류

post-casper.mov

close #120

✏️ 한 일

  • not found 페이지 생성
  • error boundary 컴포넌트 생성
  • fetchWithTimeout util 구현
  • Error Boundary 처리 구현

❗️ 발생한 이슈 (해결 방안)

fetchWithTimeout

API 오류를 발생시키기 위해서 API 주소를 임의로 바꿔서 테스트를 해봤는데, 응답이 돌아오지 않는 경우 계속해서 응답을 기다리는 문제가 있었습니다. fetch는 timeout 기능을 자체적으로 지원하지 않고 AbortController(웹 요청을 취소할 수 있는 API를 제공)를 통해서 구현이 가능하길래 AbortController를 활용한 fetch util을 구현했습니다.

export async function fetchWithTimeout(url: string, options: RequestInit = {}, timeout = 5000) {
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), timeout);
    options.signal = controller.signal;

    try {
        const response = await fetch(url, options);
        clearTimeout(id);
        return response;
    } catch (error) {
        clearTimeout(id);
        throw error;
    }
}

위와 같이 AbortController를 선언하고, signal 신호 객체를 사용해서 fetch 요청과 controller를 연결합니다. timeout으로 지정한 초가 지났는데 아직 신호가 연결 중이라면 controller를 abort해서 요청을 끊어버립니다.

ErrorBoundary

요류가 발생한 경우 오류 화면을 보여주고 싶은데, loader로 요청하는 API랑 컴포넌트 내부에서 요청하는 API랑 다르게 처리해야했습니다.

loader로 요청하는 API

{
    path: "show-case",
    element: <CasperShowCase />,
    loader: LotteryAPI.getCasperList,
    errorElement: <ErrorBoundary isError />,
},

loader에서 발생하는 오류는 react-router-dom에서 오류를 catch해서 errorElement를 띄울 수 있습니다.

컴포넌트 내부에서 요청하는 API

반면에 컴포넌트 내부에서 요청하는 API의 경우 react-error-boundary(렌더링, 코드 내부 에러 등을 감지) 등으로 잡을 수 없기 때문에 error 상태를 따로 관리해줄 필요가 있었습니다.

export default function useFetch<T, P = void>(fetch: (params: P) => Promise<T>) {
    const [data, setData] = useState<T | null>(null);
    const [isSuccess, setIsSuccess] = useState<boolean>(false);
    const [isError, setIsError] = useState<boolean>(false);

    const fetchData = async (params?: P) => {
        try {
            const data = await fetch(params as P);
            setData(data);
            setIsSuccess(!!data);
        } catch (error) {
            setIsError(true);
            console.error(error);
        }
    };

    return { data, isSuccess, isError, fetchData };
}

위와 같이 hook을 만들어서 error 상태를 관리하고, ErrorBoundary에서 isError가 true인 경우 오류 화면을 반환할 수 있게 합니다.

export default function ErrorBoundary({
    isError,
    fallbackUrl = "/",
    children,
}: ErrorBoundaryProps) {
    if (isError) {
        return (
            <div className="fixed z-10 h-screen w-full bg-n-neutral-950 flex flex-col justify-center items-center">
                <img alt="오류 아이콘" src="/assets/icons/casper-error.svg" />
                <div className="mt-4" />
                <h3 className="h-heading-3-bold text-n-white">
                    문제가 발생했습니다. 잠시 후 다시 시도해 보세요.
                </h3>
                <div className="mt-12" />
                <CTAButton label="돌아가기" url={fallbackUrl} hasArrowIcon />
            </div>
        );
    }

    return children;
}

추후 다른 API 요청이 추가되면 useFetch hook을 사용해서 api 요청을 하고 반환된 isError 상태를 ErrorBoundary 컴포넌트에 넣어주면 됩니당

❓ 논의가 필요한 사항

@jhj2713 jhj2713 added the feat 기능 구현 label Aug 8, 2024
@jhj2713 jhj2713 requested a review from sooyeoniya August 8, 2024 08:43
@jhj2713 jhj2713 self-assigned this Aug 8, 2024
Copy link
Member

@sooyeoniya sooyeoniya left a comment

Choose a reason for hiding this comment

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

완전 고생하셨습니다 😃
AbortController라는게 있는 줄도 몰랐어용,,, 역시 대단하십니다 :)


<Battery applyCount={applyCount} />
<ErrorBoundary isError={isErrorgetApplyCount}>
<motion.div className="mt-[60px] flex flex-col items-center" {...DISSOLVE}>
Copy link
Member

Choose a reason for hiding this comment

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

이거 {...SCROLL_MOTION(DISSOLVE)} 로 바꿔야할 것 같아여!

Comment on lines +28 to +33
const {
data: applyCountData,
isError: isErrorgetApplyCount,
fetchData: getApplyCount,
} = useFetch<GetApplyCountResponse>(() => LotteryAPI.getApplyCount(cookies[COOKIE_TOKEN_KEY]));

Copy link
Member

Choose a reason for hiding this comment

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

저도 useFetch 써서 해볼게용!!

Comment on lines +1 to +30
import { ReactNode } from "react";
import CTAButton from "@/components/CTAButton";

interface ErrorBoundaryProps {
isError: boolean;
fallbackUrl?: string;
children?: ReactNode;
}

export default function ErrorBoundary({
isError,
fallbackUrl = "/",
children,
}: ErrorBoundaryProps) {
if (isError) {
return (
<div className="fixed z-10 h-screen w-full bg-n-neutral-950 flex flex-col justify-center items-center">
<img alt="오류 아이콘" src="/assets/icons/casper-error.svg" />
<div className="mt-4" />
<h3 className="h-heading-3-bold text-n-white">
문제가 발생했습니다. 잠시 후 다시 시도해 보세요.
</h3>
<div className="mt-12" />
<CTAButton label="돌아가기" url={fallbackUrl} hasArrowIcon />
</div>
);
}

return children;
}
Copy link
Member

Choose a reason for hiding this comment

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

UI 없었는데 직접 만드신거 완전 예뻐용!!

Comment on lines 29 to +32
index: true,
element: <Lottery />,
loader: LotteryAPI.getLottery,
errorElement: <ErrorBoundary isError />,
Copy link
Member

Choose a reason for hiding this comment

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

저두 promise.all 로 묶어서 loader랑 errorElement 사용해서 수정해보겠습니다!

handleValueChange={handleSetExpectations}
/>
<ErrorBoundary isError={isErrorPostCasper}>
<motion.div className="flex flex-col items-center" {...DISSOLVE}>
Copy link
Member

Choose a reason for hiding this comment

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

요기 DISSOLVE도용

@jhj2713 jhj2713 merged commit 81f7e29 into dev Aug 9, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat 기능 구현
Projects
None yet
Development

Successfully merging this pull request may close these issues.

에러 핸들링 처리 추가
2 participants