Skip to content

Commit 389a25d

Browse files
committed
feat: asyncBoundary를 이용하여 에러 처리
1 parent de98c0e commit 389a25d

File tree

9 files changed

+302
-62
lines changed

9 files changed

+302
-62
lines changed

src/app/match/[id]/page.tsx

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use client';
22

3-
import { Suspense, useRef, useState } from 'react';
3+
import { useRef, useState } from 'react';
44

5+
import AsyncBoundary from '@/components/common/AsyncBoundary';
6+
import Loader from '@/components/common/Loader';
57
import MatchBanner from '@/components/match/Banner';
68
import Cheer from '@/components/match/Cheer';
79
import CommentForm from '@/components/match/CommentForm';
@@ -54,72 +56,99 @@ export default function Match({ params }: { params: { id: string } }) {
5456

5557
return (
5658
<section>
57-
<Suspense fallback={<div>배너 로딩중...</div>}>
59+
<AsyncBoundary
60+
errorFallback={props => <MatchBanner.ErrorFallback {...props} />}
61+
loadingFallback={<div>배너 로딩중...</div>}
62+
>
5863
<MatchByIdFetcher matchId={params.id}>
5964
{data => <MatchBanner {...data} />}
6065
</MatchByIdFetcher>
61-
</Suspense>
62-
<Suspense fallback={<div>응원 로딩중...</div>}>
66+
</AsyncBoundary>
67+
<AsyncBoundary
68+
errorFallback={props => <Cheer.ErrorFallback {...props} />}
69+
loadingFallback={<div>응원 로딩중...</div>}
70+
>
6371
<MatchCheerByIdFetcher matchId={params.id}>
6472
{data => <Cheer cheers={data} />}
6573
</MatchCheerByIdFetcher>
66-
</Suspense>
67-
74+
</AsyncBoundary>
6875
<Panel options={options} defaultValue="라인업">
6976
{({ selected }) => (
70-
<Suspense fallback={<div>로딩중...</div>}>
77+
<>
7178
{selected === '라인업' && (
72-
<MatchLineupFetcher matchId={params.id}>
73-
{([firstTeam, secondTeam]) => (
74-
<div className="grid grid-cols-2 py-5 [&>*:first-child>ul]:before:absolute [&>*:first-child>ul]:before:right-0 [&>*:first-child>ul]:before:h-full [&>*:first-child>ul]:before:border-l-2 [&>*:first-child>ul]:before:bg-gray-2">
75-
<Lineup {...firstTeam} />
76-
<Lineup {...secondTeam} />
77-
</div>
78-
)}
79-
</MatchLineupFetcher>
79+
<AsyncBoundary
80+
errorFallback={props => <Lineup.ErrorFallback {...props} />}
81+
loadingFallback={<Loader />}
82+
>
83+
<MatchLineupFetcher matchId={params.id}>
84+
{([firstTeam, secondTeam]) => (
85+
<div className="grid grid-cols-2 py-5 [&>*:first-child>ul]:before:absolute [&>*:first-child>ul]:before:right-0 [&>*:first-child>ul]:before:h-full [&>*:first-child>ul]:before:border-l-2 [&>*:first-child>ul]:before:bg-gray-2">
86+
<Lineup {...firstTeam} />
87+
<Lineup {...secondTeam} />
88+
</div>
89+
)}
90+
</MatchLineupFetcher>
91+
</AsyncBoundary>
8092
)}
8193
{selected === '타임라인' && (
82-
<MatchTimelineFetcher matchId={params.id}>
83-
{([firstHalf, secondHalf]) => (
84-
<div className="overflow-y-auto p-5">
85-
<RecordList {...firstHalf} />
86-
<RecordList {...secondHalf} />
87-
</div>
88-
)}
89-
</MatchTimelineFetcher>
94+
<AsyncBoundary
95+
errorFallback={props => <RecordList.ErrorFallback {...props} />}
96+
loadingFallback={<Loader />}
97+
>
98+
<MatchTimelineFetcher matchId={params.id}>
99+
{([firstHalf, secondHalf]) => (
100+
<div className="overflow-y-auto p-5">
101+
<RecordList {...firstHalf} />
102+
<RecordList {...secondHalf} />
103+
</div>
104+
)}
105+
</MatchTimelineFetcher>
106+
</AsyncBoundary>
90107
)}
91108
{selected === '응원댓글' && (
92-
<MatchCommentFetcher matchId={params.id}>
93-
{({ commentList, ...data }) => (
94-
<div className="max-h-[450px] overflow-y-auto p-5">
95-
<ul className="pb-8">
96-
<CommentList
97-
commentList={commentList.pages.flat()}
109+
<AsyncBoundary
110+
errorFallback={props => (
111+
<CommentList.ErrorFallback {...props} />
112+
)}
113+
loadingFallback={<Loader />}
114+
>
115+
<MatchCommentFetcher matchId={params.id}>
116+
{({ commentList, ...data }) => (
117+
<div className="max-h-[450px] overflow-y-auto p-5">
118+
<ul className="pb-8">
119+
<CommentList
120+
commentList={commentList.pages.flat()}
121+
scrollToBottom={scrollToBottom}
122+
{...data}
123+
/>
124+
<CommentList.SocketList commentList={comments} />
125+
<li ref={scrollRef}></li>
126+
</ul>
127+
<CommentForm
128+
matchId={params.id}
129+
mutate={mutate}
98130
scrollToBottom={scrollToBottom}
99-
{...data}
100131
/>
101-
<CommentList.SocketList commentList={comments} />
102-
<li ref={scrollRef}></li>
103-
</ul>
104-
<CommentForm
105-
matchId={params.id}
106-
mutate={mutate}
107-
scrollToBottom={scrollToBottom}
108-
/>
109-
</div>
110-
)}
111-
</MatchCommentFetcher>
132+
</div>
133+
)}
134+
</MatchCommentFetcher>
135+
</AsyncBoundary>
112136
)}
113137
{selected === '경기영상' && (
114-
<MatchVideoFetcher matchId={params.id}>
115-
{data => (
116-
<div className="overflow-y-auto p-5">
117-
<Video {...data} />
118-
</div>
119-
)}
120-
</MatchVideoFetcher>
138+
<AsyncBoundary
139+
errorFallback={props => <Video.ErrorFallback {...props} />}
140+
loadingFallback={<Loader />}
141+
>
142+
<MatchVideoFetcher matchId={params.id}>
143+
{data => (
144+
<div className="overflow-y-auto p-5">
145+
<Video {...data} />
146+
</div>
147+
)}
148+
</MatchVideoFetcher>
149+
</AsyncBoundary>
121150
)}
122-
</Suspense>
151+
</>
123152
)}
124153
</Panel>
125154
</section>

src/app/page.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use client';
22

3-
import { Suspense } from 'react';
4-
3+
import AsyncBoundary from '@/components/common/AsyncBoundary';
54
import SportsList from '@/components/league/SportsList';
65
import MatchList from '@/components/match/MatchList';
76
import { QUERY_PARAMS } from '@/constants/queryParams';
@@ -22,7 +21,10 @@ export default function Home() {
2221

2322
return (
2423
<section className="flex flex-col items-center">
25-
<Suspense>
24+
<AsyncBoundary
25+
errorFallback={() => <div>에러</div>}
26+
loadingFallback={<div>로딩 중</div>}
27+
>
2628
<SportsListFetcher leagueId={params.get(QUERY_PARAMS.league) || '1'}>
2729
{data => (
2830
<SportsList
@@ -32,7 +34,7 @@ export default function Home() {
3234
/>
3335
)}
3436
</SportsListFetcher>
35-
</Suspense>
37+
</AsyncBoundary>
3638

3739
<div className="mb-8 flex w-fit items-center gap-5 rounded-xl bg-gray-2 text-center">
3840
<button
@@ -67,15 +69,18 @@ export default function Home() {
6769
</button>
6870
</div>
6971

70-
<div className="flex flex-col gap-8">
71-
<Suspense fallback={<div>MatchList 로딩중...</div>}>
72-
<MatchListFetcher {...paramsObj}>
73-
{({ matchList, ...props }) => (
72+
<AsyncBoundary
73+
errorFallback={props => <MatchList.ErrorFallback {...props} />}
74+
loadingFallback={<div>로딩 중</div>}
75+
>
76+
<MatchListFetcher {...paramsObj}>
77+
{({ matchList, ...props }) => (
78+
<div className="flex w-full flex-col gap-8">
7479
<MatchList matchList={matchList.pages.flat()} {...props} />
75-
)}
76-
</MatchListFetcher>
77-
</Suspense>
78-
</div>
80+
</div>
81+
)}
82+
</MatchListFetcher>
83+
</AsyncBoundary>
7984
</section>
8085
);
8186
}

src/components/match/Banner/index.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FallbackProps } from '@/components/common/ErrorBoundary';
12
import { MatchCard } from '@/components/common/MatchCard';
23
import { MatchType } from '@/types/match';
34

@@ -21,3 +22,19 @@ export default function MatchBanner(props: MatchType) {
2122
</MatchCard>
2223
);
2324
}
25+
26+
MatchBanner.ErrorFallback = function ErrorFallback({
27+
resetErrorBoundary,
28+
}: FallbackProps) {
29+
return (
30+
<div className="relative my-5 flex h-full min-h-[200px] w-full flex-col items-center justify-center gap-5 rounded-xl border p-2">
31+
<div className="flex flex-wrap justify-center gap-x-1">
32+
<span>게임 정보를 불러올 수 없어요.</span>
33+
<span>잠시 후 다시 시도해주세요!</span>
34+
</div>
35+
<button onClick={resetErrorBoundary} className="text-primary">
36+
새로고침
37+
</button>
38+
</div>
39+
);
40+
};

src/components/match/Cheer/index.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FallbackProps } from '@/components/common/ErrorBoundary';
12
import { MatchCheerType } from '@/types/match';
23

34
import CheerTeam from '../CheerTeam';
@@ -25,3 +26,19 @@ export default function Cheer({ cheers }: CheerProps) {
2526
</div>
2627
);
2728
}
29+
30+
Cheer.ErrorFallback = function ErrorFallback({
31+
resetErrorBoundary,
32+
}: FallbackProps) {
33+
return (
34+
<div className="min-h-10 relative my-5 flex h-full w-full flex-col items-center justify-center gap-5 p-2">
35+
<div className="flex flex-wrap justify-center gap-x-1">
36+
<span>응원하기를 불러올 수 없어요. </span>
37+
<span>잠시 후 다시 시도해주세요!</span>
38+
</div>
39+
<button onClick={resetErrorBoundary} className="text-primary">
40+
새로고침
41+
</button>
42+
</div>
43+
);
44+
};

src/components/match/CommentList/index.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
'use client';
22

3+
import { AxiosError } from 'axios';
34
import { useEffect } from 'react';
45

6+
import { FallbackProps } from '@/components/common/ErrorBoundary';
7+
import { COMMENT_API_ERROR_MESSAGE } from '@/constants/error';
58
import useInfiniteObserver from '@/hooks/useInfiniteObserver';
69
import { MatchCommentType } from '@/types/match';
710

@@ -58,3 +61,32 @@ CommentList.SocketList = function SocketList({
5861
</>
5962
);
6063
};
64+
65+
CommentList.ErrorFallback = function ErrorFallback({
66+
error,
67+
resetErrorBoundary,
68+
}: FallbackProps) {
69+
let message;
70+
71+
if (error instanceof AxiosError) {
72+
const code = error.code;
73+
74+
message =
75+
COMMENT_API_ERROR_MESSAGE[code as keyof typeof COMMENT_API_ERROR_MESSAGE];
76+
} else if (error instanceof Error) {
77+
message = '댓글 목록을 불러올 수가 없어요!';
78+
}
79+
80+
return (
81+
<div className="flex h-full w-full flex-col items-center justify-center gap-5 rounded-xl py-10">
82+
<span className="text-gary-5">⚠️ {message}</span>
83+
84+
<button
85+
onClick={resetErrorBoundary}
86+
className="text-primary underline-offset-1"
87+
>
88+
새로고침
89+
</button>
90+
</div>
91+
);
92+
};

src/components/match/LineupList/index.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { AxiosError } from 'axios';
2+
3+
import { FallbackProps } from '@/components/common/ErrorBoundary';
4+
import { LINEUP_API_ERROR_MESSAGE } from '@/constants/error';
15
import { MatchLineupType } from '@/types/match';
26

37
import LineupItem from '../LineupItem';
@@ -14,3 +18,32 @@ export default function Lineup({ teamName, gameTeamPlayers }: MatchLineupType) {
1418
</div>
1519
);
1620
}
21+
22+
Lineup.ErrorFallback = function ErrorFallback({
23+
error,
24+
resetErrorBoundary,
25+
}: FallbackProps) {
26+
let message;
27+
28+
if (error instanceof AxiosError) {
29+
const code = error.code;
30+
31+
message =
32+
LINEUP_API_ERROR_MESSAGE[code as keyof typeof LINEUP_API_ERROR_MESSAGE];
33+
} else if (error instanceof Error) {
34+
message = '라인업이 등록되지 않았어요!';
35+
}
36+
37+
return (
38+
<div className="flex h-full w-full flex-col items-center justify-center gap-5 rounded-xl py-10">
39+
<span className="text-gary-5">⚠️ {message}</span>
40+
41+
<button
42+
onClick={resetErrorBoundary}
43+
className="text-primary underline-offset-1"
44+
>
45+
새로고침
46+
</button>
47+
</div>
48+
);
49+
};

0 commit comments

Comments
 (0)