-
Notifications
You must be signed in to change notification settings - Fork 5
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
[FE] react-query에 맞는 error handling 적용 #415
Merged
Merged
Changes from 33 commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
d351175
chore: 디자인시스템 버전 적용
Todari ca35386
rename: 파일 이름 변경
Todari eab610b
fix: 사용하지 않는 Error 처리 로직 삭제
Todari a439e5f
feat: 전역 에러 상태 생성
Todari 224941e
fix: 바뀐 파일 이름 적용
Todari 910b555
fix: ToastProvider 내에서 에러 처리 로직 제거
Todari 59597a7
refactor: AppErrorBoundary 및 QueryClientBoundary를 통한 에러 처리
Todari 296205c
fix: 사용하지 않는 코드 임시적으로 주석 처리
Todari 7e964e1
mover: AppErrorBoundary, QueryClientBoundary 코드 위치 변경
Todari 78bb088
fix: test 코드 변경
Todari 3fa2cfa
chore: jest path alias 적용
Todari f32b9b6
test: AppErrorBoundary 테스트코드 작성
Todari 031d8b5
remove: 사용하지 않는 코드 삭제
Todari 6a9aabc
fix: 사용하지 않는 코드 주석처리
Todari 84aa761
fix: AppErrorBoundary, QueryClientBoundary 로직 수정
Todari bc5d57d
test: AppErrorBoundary test코드 작성
Todari 90229d4
style: lint 적용
Todari aec4174
feat: App에 ErrorBoundary 추가
pakxe 785b82f
feat: UnhandledErrorBoundary 컴포넌트 구현
pakxe be305a1
feat: 에러를 구독하며 핸들링되는 에러면 토스트, 핸들링 불가능한 에러면 에러 바운더리를 띄우도록 하는 캐처 구현
pakxe facd363
feat: 에러 페이지에 메일 추가
pakxe eb81923
feat: errorInfo를 안에서 구현하는 것이 아닌 구현된 것을 인자로 받도록 수정
pakxe 39cc30b
chore: 파일 위치 수정에 따라 import 경로 수정
pakxe 727bb81
chore: 불필요해진 파일 제거
pakxe 0c415e6
Merge branch 'fe-dev' of https://github.com/woowacourse-teams/2024-ha…
pakxe 9262326
chore: 개행 추가
pakxe fff00ad
feat: ErrorCatcher에 대한 테스트 코드 작성
pakxe ed5e847
feat: Toast의 showingTime 을 옵셔널로 수정
pakxe fd723ff
chore: 없어진 컴포넌트를 renderHook에서 제거
pakxe 4c2fee2
refactor: return문이 반복되는 부분을 리펙터링
pakxe 973e4eb
feat: useToast에 대한 테스트코드 작성
pakxe 2fe3e97
chore: 사용하지 않는 파일 제거
pakxe f6f24d2
chore: 불필요한 파일이 커버리지에 뜨지 않도록 ignore에 추가
pakxe 7aa7ae8
Merge branch 'fe-dev' of https://github.com/woowacourse-teams/2024-ha…
pakxe a8b8297
rename: 파일명 오타 수정
pakxe ce53134
chore: import 경로 수정
pakxe 3b55284
chore: 병힙
pakxe 056aee1
chore: 라이브러리 오류로 인해 테스트 통과가 안되므로 디자인 시스템 라이브러리 다운그레이드
pakxe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
client/src/components/AppErrorBoundary/ErrorCatcher.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import {render, screen, waitFor} from '@testing-library/react'; | ||
import {act, ReactNode} from 'react'; | ||
import {MemoryRouter} from 'react-router-dom'; | ||
import {HDesignProvider} from 'haengdong-design'; | ||
|
||
import FetchError from '@errors/FetchError'; | ||
import {ToastProvider} from '@hooks/useToast/ToastProvider'; | ||
|
||
import {useAppErrorStore} from '@store/appErrorStore'; | ||
|
||
import {SERVER_ERROR_MESSAGES} from '@constants/errorMessage'; | ||
|
||
import UnhandledErrorBoundary from '../../UnhandledErrorBoudnary'; | ||
|
||
import ErrorCatcher from './ErrorCatcher'; | ||
|
||
// 테스트용 헬퍼 컴포넌트 | ||
const TestComponent = ({triggerError}: {triggerError: () => void}) => { | ||
return <button onClick={triggerError}>Trigger Error</button>; | ||
}; | ||
|
||
const setup = (ui: ReactNode) => | ||
render( | ||
<HDesignProvider> | ||
<UnhandledErrorBoundary> | ||
<ToastProvider> | ||
<ErrorCatcher> | ||
<MemoryRouter>{ui}</MemoryRouter> | ||
</ErrorCatcher> | ||
</ToastProvider> | ||
</UnhandledErrorBoundary> | ||
</HDesignProvider>, | ||
); | ||
|
||
describe('ErrorCatcher', () => { | ||
jest.mock('react-router-dom', () => ({ | ||
...jest.requireActual('react-router-dom'), | ||
useNavigate: jest.fn(), | ||
})); | ||
|
||
it('핸들링 가능한 에러인 경우 토스트가 표시된다.', async () => { | ||
const errorCode = 'EVENT_NOT_FOUND'; | ||
const error = new FetchError({ | ||
errorInfo: {errorCode, message: '서버의 에러메세지'}, | ||
name: errorCode, | ||
message: '에러메세지', | ||
status: 200, | ||
endpoint: '', | ||
method: 'GET', | ||
requestBody: '', | ||
}); | ||
|
||
const {updateAppError} = useAppErrorStore.getState(); | ||
|
||
setup(<TestComponent triggerError={() => updateAppError(error)} />); | ||
|
||
act(() => { | ||
screen.getByText('Trigger Error').click(); | ||
}); | ||
|
||
const errorMessage = SERVER_ERROR_MESSAGES[errorCode]; | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText(errorMessage)).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('핸들링 불가능한 에러인 경우 에러 바운더리가 표시된다.', async () => { | ||
const errorCode = '모르겠는 에러'; | ||
const error = new FetchError({ | ||
errorInfo: {errorCode, message: '모르겠는 에러메세지'}, | ||
name: errorCode, | ||
message: '에러메세지', | ||
status: 400, | ||
endpoint: '', | ||
method: 'GET', | ||
requestBody: '', | ||
}); | ||
|
||
const {updateAppError} = useAppErrorStore.getState(); | ||
|
||
setup(<TestComponent triggerError={() => updateAppError(error)} />); | ||
|
||
act(() => { | ||
screen.getByText('Trigger Error').click(); | ||
}); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText('알 수 없는 오류입니다.')).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import {useEffect} from 'react'; | ||
|
||
import FetchError from '@errors/FetchError'; | ||
import {useToast} from '@hooks/useToast/useToast'; | ||
|
||
import {useAppErrorStore} from '@store/appErrorStore'; | ||
|
||
import {captureError} from '@utils/captureError'; | ||
|
||
import {SERVER_ERROR_MESSAGES, UNKNOWN_ERROR} from '@constants/errorMessage'; | ||
|
||
export type ErrorInfo = { | ||
errorCode: string; | ||
message: string; | ||
}; | ||
|
||
const convertAppErrorToErrorInfo = (appError: Error) => { | ||
if (appError instanceof Error) { | ||
const errorInfo = | ||
appError instanceof FetchError ? appError.errorInfo : {errorCode: appError.name, message: appError.message}; | ||
|
||
return errorInfo; | ||
} else { | ||
const errorInfo = {errorCode: UNKNOWN_ERROR, message: JSON.stringify(appError)}; | ||
|
||
return errorInfo; | ||
} | ||
}; | ||
|
||
const isUnhandledError = (errorInfo: ErrorInfo) => { | ||
if (errorInfo.errorCode === 'INTERNAL_SERVER_ERROR') return true; | ||
|
||
return SERVER_ERROR_MESSAGES[errorInfo.errorCode] === undefined; | ||
}; | ||
|
||
const ErrorCatcher = ({children}: React.PropsWithChildren) => { | ||
const {appError} = useAppErrorStore(); | ||
const {showToast} = useToast(); | ||
|
||
useEffect(() => { | ||
if (appError) { | ||
const errorInfo = convertAppErrorToErrorInfo(appError); | ||
captureError(appError, errorInfo); | ||
|
||
if (!isUnhandledError(errorInfo)) { | ||
showToast({ | ||
showingTime: 3000, | ||
message: SERVER_ERROR_MESSAGES[errorInfo.errorCode], | ||
type: 'error', | ||
position: 'bottom', | ||
bottom: '8rem', | ||
}); | ||
} else { | ||
throw appError; | ||
} | ||
} | ||
}, [appError]); | ||
|
||
return children; | ||
}; | ||
|
||
export default ErrorCatcher; |
24 changes: 24 additions & 0 deletions
24
client/src/components/QueryClientBoundary/QueryClientBoundary.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import {MutationCache, QueryCache, QueryClient, QueryClientProvider} from '@tanstack/react-query'; | ||
|
||
import {useAppErrorStore} from '@store/appErrorStore'; | ||
|
||
const QueryClientBoundary = ({children}: React.PropsWithChildren) => { | ||
const {updateAppError} = useAppErrorStore(); | ||
|
||
const queryClient = new QueryClient({ | ||
queryCache: new QueryCache({ | ||
onError: (error: Error) => { | ||
updateAppError(error); | ||
}, | ||
}), | ||
mutationCache: new MutationCache({ | ||
onError: (error: Error) => { | ||
updateAppError(error); | ||
}, | ||
}), | ||
Comment on lines
+9
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 너무 좋아요! 기존의 에러 처리방식보다 확실히 더 좋아진 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. useFetch가 useQuery, useMutation으로 대체되며 더 신경쓸 필요가 적어진 것 같아요 ㅎ.ㅎㅎㅎ |
||
}); | ||
|
||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>; | ||
}; | ||
|
||
export default QueryClientBoundary; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
컴포넌트 명 너무 좋다! 에러를 캐치해주는 녀석
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
캣쮜