Skip to content

Commit

Permalink
Merge pull request #148 from woowacourse-teams/feature/CK-187
Browse files Browse the repository at this point in the history
[feat/CK-187] Error-Boundary를 활용한 에러핸들링(초안)
  • Loading branch information
sh981013s authored Sep 14, 2023
2 parents 34b94fc + feb1c03 commit d00b3e4
Show file tree
Hide file tree
Showing 15 changed files with 341 additions and 30 deletions.
3 changes: 3 additions & 0 deletions client/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ module.exports = {
'react/jsx-no-useless-fragment': 'off',
'react/no-unescaped-entities': 'off',
'no-else-return': 'off',
'react/no-unstable-nested-components': 'off',
'react/no-unused-class-component-methods': 'off',
'react/destructuring-assignment': 'off',
'consistent-return': 'off',
},
};
6 changes: 3 additions & 3 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import UserInfoProvider, {
} from './components/_providers/UserInfoProvider';
import RoadmapSearchResult from './components/roadmapListPage/roadmapSearch/RoadmapSearchResult';
import MainPage from '@pages/mainPage/MainPage';
import ErrorBoundary from '@components/_common/errorBoundary/ErrorBoundary';
import useToast from '@hooks/_common/useToast';
import AsyncBoundary from './components/_common/errorBoundary/AsyncBoundary';

const PrivateRouter = (props: PropsWithChildren) => {
const { children } = props;
Expand All @@ -49,7 +49,7 @@ const App = () => {
<BrowserRouter>
<ResponsiveContainer>
<PageLayout>
<ErrorBoundary>
<AsyncBoundary>
<Routes>
<Route path='/' element={<MainPage />} />
<Route path='/login' element={<LoginPage />} />
Expand Down Expand Up @@ -98,7 +98,7 @@ const App = () => {
}
/>
</Routes>
</ErrorBoundary>
</AsyncBoundary>
</PageLayout>
</ResponsiveContainer>
</BrowserRouter>
Expand Down
75 changes: 75 additions & 0 deletions client/src/components/_common/error/ErrorComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as S from './errorComponents.styles';
import elephantImage from '../../../assets/images/cryingelephant.png';
import { useNavigate } from 'react-router-dom';

export const NotFound = () => {
const navigate = useNavigate();

const moveMainPage = () => {
navigate('/');
window.location.reload();
};
return (
<S.Container>
<S.ElephantImage src={elephantImage} alt='crying-elephant' />
<S.NotFoundTitle>404 Not Found</S.NotFoundTitle>
<S.NotFoundText>잘못된 경로 접근했어요</S.NotFoundText>
<S.MovePageButton onClick={moveMainPage}>메인페이지로 돌아가기</S.MovePageButton>
</S.Container>
);
};

export const ServerError = () => {
const navigate = useNavigate();

const moveMainPage = () => {
navigate('/');
window.location.reload();
};
return (
<S.Container>
<S.ElephantImage src={elephantImage} alt='crying-elephant' />
<S.SereverTitle>500 Error</S.SereverTitle>
<S.ServerText>서버에서 오류가 발생했어요</S.ServerText>
<S.MovePageButton onClick={moveMainPage}>메인페이지로 돌아가기</S.MovePageButton>
</S.Container>
);
};

export const Runtime = () => {
const navigate = useNavigate();

const moveMainPage = () => {
navigate('/');
window.location.reload();
};
return (
<S.Container>
<S.ElephantImage src={elephantImage} alt='crying-elephant' />
<S.RuntimeTitle>Error</S.RuntimeTitle>
<S.RuntimeText>
죄송합니다, 페이지를 로드하는 동안 오류가 발생했습니다
</S.RuntimeText>
<S.MovePageButton onClick={moveMainPage}>메인페이지로 돌아가기</S.MovePageButton>
</S.Container>
);
};

export const Critical = () => {
const navigate = useNavigate();

const moveMainPage = () => {
navigate('/');
window.location.reload();
};
return (
<S.Container>
<S.ElephantImage src={elephantImage} alt='crying-elephant' />
<S.CriticalTitle>Service Not Working</S.CriticalTitle>
<S.CriticalText>
죄송합니다, 알 수 없는 이유로 서비스 사용이 불가능합니다
</S.CriticalText>
<S.MovePageButton onClick={moveMainPage}>메인페이지로 돌아가기</S.MovePageButton>
</S.Container>
);
};
70 changes: 70 additions & 0 deletions client/src/components/_common/error/errorComponents.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import styled from 'styled-components';

export const Container = styled.section`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
`;

export const ElephantImage = styled.img`
width: 20%;
`;

export const NotFoundTitle = styled.h1`
${({ theme }) => theme.fonts.title_large};
font-size: 4rem;
color: ${({ theme }) => theme.colors.main_dark};
`;

export const NotFoundText = styled.h2`
${({ theme }) => theme.fonts.h2};
color: ${({ theme }) => theme.colors.gray300};
`;

export const SereverTitle = styled.h1`
${({ theme }) => theme.fonts.title_large};
font-size: 4rem;
color: ${({ theme }) => theme.colors.main_dark};
`;

export const ServerText = styled.h2`
${({ theme }) => theme.fonts.h2};
color: ${({ theme }) => theme.colors.gray300};
`;

export const RuntimeTitle = styled.h1`
${({ theme }) => theme.fonts.title_large};
font-size: 4rem;
color: ${({ theme }) => theme.colors.main_dark};
`;

export const RuntimeText = styled.h2`
${({ theme }) => theme.fonts.h2};
color: ${({ theme }) => theme.colors.gray300};
`;

export const CriticalTitle = styled.h1`
${({ theme }) => theme.fonts.title_large};
font-size: 4rem;
color: ${({ theme }) => theme.colors.main_dark};
`;

export const CriticalText = styled.h2`
${({ theme }) => theme.fonts.h2};
color: ${({ theme }) => theme.colors.gray300};
`;

export const MovePageButton = styled.button`
${({ theme }) => theme.fonts.button1}
margin: 2rem 0;
padding: 1rem 2rem;
color: ${({ theme }) => theme.colors.white};
background-color: ${({ theme }) => theme.colors.main_middle};
border-radius: 30px;
`;
22 changes: 22 additions & 0 deletions client/src/components/_common/errorBoundary/AsyncBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { PropsWithChildren, Suspense } from 'react';
import Spinner from '../spinner/Spinner';
import CriticalErrorBoundary from './CriticalErrorBoundary';
import NotFoundErrorBoundary from './NotFoundErrorBoundary';
import RuntimeErrorBoundary from './RuntimeErrorBoundary';
import ServerErrorBoundary from './ServerErrorBoundary';

const AsyncBoundary = ({ children }: PropsWithChildren) => {
return (
<CriticalErrorBoundary>
<RuntimeErrorBoundary>
<ServerErrorBoundary>
<NotFoundErrorBoundary>
<Suspense fallback={<Spinner />}>{children}</Suspense>
</NotFoundErrorBoundary>
</ServerErrorBoundary>
</RuntimeErrorBoundary>
</CriticalErrorBoundary>
);
};

export default AsyncBoundary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ErrorInfo } from 'react';
import { Critical } from '../error/ErrorComponents';
import ErrorBoundary from './ErrorBoundary';

class CriticalErrorBoundary extends ErrorBoundary {
componentDidCatch(_error: any, _errorInfo: ErrorInfo): void {}

render() {
const { didCatch } = this.state;
const { children } = this.props;
if (didCatch) {
return <Critical />;
}
return children;
}
}

export default CriticalErrorBoundary;
38 changes: 13 additions & 25 deletions client/src/components/_common/errorBoundary/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
import { Component, ReactNode } from 'react';
import ErrorBoundaryFallback from '@components/_common/errorBoundary/ErrorBoundaryFallback';
import { ErrorBoundaryProps, ErrorBoundaryState } from '@/myTypes/_common/errorBoundary';
import { Component } from 'react';

type MyError = {
message: string;
const initialState: ErrorBoundaryState = {
didCatch: false,
error: null,
};

type ErrorBoundaryProps = {
children: ReactNode;
};

type ErrorBoundaryState = {
hasError: boolean;
error: MyError | null;
};

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
class ErrorBoundary extends Component<any, any> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}

static getDerivedStateFromError(error: MyError) {
return { hasError: true, error };
this.state = initialState;
}

render() {
const { hasError, error } = this.state;
const { children } = this.props;
static getDerivedStateFromError(error: Error) {
return { didCatch: true, error };
}

if (hasError && error) {
return <ErrorBoundaryFallback errorMessage={error.message} />;
resetError() {
if (this.state.didCatch) {
this.setState(initialState);
}

return children;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ErrorInfo } from 'react';
import { NotFound } from '../error/ErrorComponents';
import ErrorBoundary from './ErrorBoundary';

class NotFoundErrorBoundary extends ErrorBoundary {
componentDidCatch(error: any, _errorInfo: ErrorInfo): void {
if (error.response.status !== 404) throw error;
}

render() {
const { didCatch } = this.state;
const { children } = this.props;
if (didCatch) {
return <NotFound />;
}
return children;
}
}

export default NotFoundErrorBoundary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ErrorInfo } from 'react';
import { Runtime } from '../error/ErrorComponents';
import ErrorBoundary from './ErrorBoundary';

class RuntimeErrorBoundary extends ErrorBoundary {
componentDidCatch(_error: any, _errorInfo: ErrorInfo): void {}

render() {
const { didCatch } = this.state;
const { children } = this.props;
if (didCatch) {
return <Runtime />;
}
return children;
}
}

export default RuntimeErrorBoundary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SERVER_ERROR_CODE } from '@/constants/_common/regex';
import { ErrorInfo } from 'react';
import { ServerError } from '../error/ErrorComponents';
import ErrorBoundary from './ErrorBoundary';

class ServerErrorBoundary extends ErrorBoundary {
componentDidCatch(error: any, _errorInfo: ErrorInfo): void {
if (!SERVER_ERROR_CODE.test(error.response.status)) throw error;
}

render() {
const { didCatch } = this.state;
const { children } = this.props;
if (didCatch) {
return <ServerError />;
}
return children;
}
}

export default ServerErrorBoundary;
1 change: 1 addition & 0 deletions client/src/constants/_common/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SERVER_ERROR_CODE = /5\d{2}/;
4 changes: 4 additions & 0 deletions client/src/context/errorboundaryContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ErrorBoundaryContextType } from '@/myTypes/_common/errorBoundary';
import { createContext } from 'react';

export const ErrorBoundaryContext = createContext<ErrorBoundaryContextType | null>(null);
1 change: 0 additions & 1 deletion client/src/hooks/queries/roadmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ export const useCreateRoadmap = () => {
await queryClient.refetchQueries([QUERY_KEYS.roadmap.list]);
navigate('/roadmap-list');
},
onError() {},
});

return {
Expand Down
9 changes: 8 additions & 1 deletion client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,15 @@ const startApp = async () => {
staleTime: 1000 * 60 * 5,
cacheTime: 1000 * 60 * 30,
},

mutations: {
useErrorBoundary: true,
useErrorBoundary: false,
onError: (e) => {
const error = e as any;
if (error.response.status === 400) {
alert(error.response.data.message);
}
},
},
},
});
Expand Down
Loading

0 comments on commit d00b3e4

Please sign in to comment.