https://tkdodo.eu/blog/react-query-error-handling
서론
리액트 쿼리는 비동기 상태 관리 라이브러리입니다.
사전 지식
표준 예제
Copythe-standard-example: copy code to clipboard
function TodoList() {
const todos = useQuery(['todos'], fetchTodos)
if (todos.isLoading) {
return 'Loading...'
}
// ✅ standard error handling
// could also check for: todos.status === 'error'
if (todos.isError) {
return 'An error occurred'
}
return (
<div>
{todos.data.map((todo) => (
<Todo key={todo.id} {...todo} />
))}
</div>
)
}
1. 백그라운드 오류를 잘 처리하지 못합니다.
2. 쿼리를 사용하는 컴포넌트마다 보일러플레이트가 쌓입니다.
Error Boundaries
에러 바운더리(Error Boundaries)라는, 렌더링 중에 발생하는 런타임 오류를 포착하기 위한 React의 개념을 사용하면
에러 발생 시 대체 UI를 표시할 수 있습니다.
우리가 원하는 크기의 컴포넌트 단위를 래핑할 수 있으므로 나머지 UI가 해당 오류의 영향을 받지 않기 때문에 좋습니다.
Error Boundaries가 할 수 없는 일이 있습니다.
렌더링 중에 발생하지 않는 비동기 오류를 잡는 것입니다.
따라서 React Query에서 Error Boundaries가 작동하도록 하기 위해 라이브러리는 내부적으로 오류를 포착하고,
다음 렌더링 주기에서 에러 바운더리가 이를 캐치할 수 있도록 다시 던집니다.
나는 이것이 오류 처리에 대한 매우 천재적이며 간단한 접근 방식이라고 생각합니다.
이 작업을 수행하려면 useErrorBoundary 플래그를 쿼리에 전달하거나 기본 설정을 통해 제공하기만 하면 됩니다.
function TodoList() {
// ✅ will propagate all fetching errors to the nearest Error Boundary
// ✅ 모든 fetching 오류를 가장 가까운 에러바운더리로 전파합니다.
const todos = useQuery(['todos'], fetchTodos, { useErrorBoundary: true })
if (todos.data) {
return (
<div>
{todos.data.map((todo) => (
<Todo key={todo.id} {...todo} />
))}
</div>
)
}
return 'Loading...'
}
v3.23.0부터는 useErrorBoundary에 함수를 제공하여
- 어떤 오류는 Error Boundary로 보내고
- 어떤 오류는 로컬에서 처리할지
선택할 수 있습니다.
useQuery(['todos'], fetchTodos, {
// 🚀 only server errors will go to the Error Boundary
useErrorBoundary: (error) => error.response?.status >= 500,
})
이것은 mutations에도 적용되며 양식 제출을 수행할 때 매우 유용합니다.
4xx 범위의 오류는 로컬에서 처리할 수 있으며(예: 일부 백엔드 유효성 검사가 실패한 경우),
모든 5xx 서버 오류는 에러바운더리로 전파될 수 있습니다.
오류 알림 표시
import toast from 'react-hot-toast'
toast.error('Something went wrong')
The onError callback
const useTodos = () =>
useQuery(['todos'], fetchTodos, {
// ⚠️ 좋아보이지만 당신이 원하는 것이 아닐 수 있어요.
onError: (error) =>
toast.error(`Something went wrong: ${error.message}`),
})
CopyuseEffect-error-toast: copy code to clipboard
const useTodos = () => {
const todos = useQuery(['todos'], fetchTodos)
// 🚨 이펙트는 이 커스텀 훅을 사용하는
// 모든 컴포넌트에서 각각 실행됩니다.
React.useEffect(() => {
if (todos.error) {
toast.error(`Something went wrong: ${todos.error.message}`)
}
}, [todos.error])
return todos
}
글로벌 콜백
새 QueryClient를 만들 때 암시적으로 발생하는 QueryCache 필드에 전역 콜백을 제공합니다.
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error) =>
toast.error(`Something went wrong: ${error.message}`),
}),
})
- 이제 각 쿼리에 대해 한 번만 오류 토스트를 표시하며
- 해당 설정을 덮어쓸 수 없습니다.
요약
1. useQuery에서 반환된 error 프로퍼티
별로 추천하지 않습니다
2. onError 콜백(쿼리 자체 또는 전역 QueryCache / MutationCache에서)
주로 4xx 오류는 로컬 콜백으로 처리합니다(5xx 에러는 useErrorBoundary 사용).
오류를 컴포넌트 각각에서 처리하지 않고 한번에 처리하거나, 사용자에게 알릴 목적이면
QueryCache / MutationCache 의 필드를 사용합니다.
3. ErrorBoundary 사용
- 오류를 캐치하여, 렌더링을 발생시켜 다음 렌더링 주기에서 에러 바운더리를 렌더링 합니다.
- 대체 UI를 표시할 수 있을 때 사용합니다.
- 저는 500번대 에러처리에 GlobalErrorBoundary를 활용한 적이 있습니다.
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
// 🎉 이미 캐시에 데이터가 있는 경우에만 오류 알림을 표시합니다.
// 이는 백그라운드 업데이트가 실패했음을 의미합니다.
if (query.state.data !== undefined) {
toast.error(`Something went wrong: ${error.message}`)
}
},
}),
})
'FrontEnd' 카테고리의 다른 글
리액트 쿼리 : 뮤테이션 (0) | 2022.06.20 |
---|---|
리액트 쿼리 : 네트워크 오프라인 시 동작 (2) | 2022.06.20 |
리액트 쿼리 : 상태 관리 라이브러리 (0) | 2022.06.19 |
리액트 쿼리 : 초기값과 플레이스홀더 (0) | 2022.06.19 |
리액트 쿼리 : Query Function Context로 queryFn 타이핑 (0) | 2022.06.19 |