Как работает обработка ошибок в React? Что такое Error Boundaries?
Error Boundary — класс с static getDerivedStateFromError и componentDidCatch, который ловит ошибки рендера, lifecycle и конструкторов потомков и показывает fallback вместо падения всего дерева. Не ловит ошибки в обработчиках событий, async-коде вне рендера и SSR hydration mismatch.
Разбор
React реализует модель «защитной границы»: ошибки в render-фазе пробрасываются вверх по дереву, пока не натолкнутся на компонент, реализующий контракт Error Boundary. Без такой границы ошибка размонтирует всё дерево, и пользователь увидит пустой экран.
Что происходит внутри
React ищет ближайшего предка с static getDerivedStateFromError(error) или componentDidCatch(error, info). Первый метод возвращает новое state — компонент рендерит fallback. Второй принимает error и info.componentStack и удобен для логирования (Sentry, Datadog).
Граница ловит:
- ошибки в render-фазе функциональных и классовых компонентов;
- ошибки в constructor/lifecycle классов;
- rejected-промисы через
use(promise), если они доходят до commit-фазы и не перехвачены вложенной границей.
Граница не ловит:
- ошибки в обработчиках событий (
onClick,onChange) — их ловят локальнымtry/catch; - ошибки в
setTimeout,fetch, async-функциях вне render-фазы; - SSR hydration mismatch — React пытается восстановиться через client-only rerender самостоятельно.
Хука для Error Boundary до сих пор нет. Класс — единственный официальный способ. Сообщество использует react-error-boundary, которая даёт функциональный API поверх класса.
Пример
import { Component, type ReactNode } from "react";
type Props = { fallback: ReactNode; children: ReactNode };
type State = { error?: Error };
export class ErrorBoundary extends Component<Props, State> {
state: State = {};
static getDerivedStateFromError(error: Error): State {
return { error };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
// Логирование в Sentry, Datadog и т.д.
console.error("[ErrorBoundary]", error, info.componentStack);
}
render() {
return this.state.error ? this.props.fallback : this.props.children;
}
}
// Использование
function WidgetPage() {
return (
<>
<ErrorBoundary fallback={<p>Не удалось загрузить виджет A</p>}>
<WidgetA />
</ErrorBoundary>
<ErrorBoundary fallback={<p>Не удалось загрузить виджет B</p>}>
<WidgetB />
</ErrorBoundary>
</>
);
}
// С react-error-boundary
import { ErrorBoundary } from "react-error-boundary";
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
<p>Ошибка: {error.message}</p>
<button onClick={resetErrorBoundary}>Попробовать снова</button>
</div>
)}
onError={(error, info) => Sentry.captureException(error, { extra: info })}
>
<HeavyComponent />
</ErrorBoundary>
Оборачивайте независимые поддеревья (виджет, страница), а не всё приложение — ошибка одного виджета не должна гасить остальные.
Подводные камни
- Граница не ловит ошибки внутри себя. Если ошибка возникает в
renderсамой границы или в её fallback, экран снова падает. Делайте fallback максимально простым, без асинхронных запросов и сложной логики. - Ошибки в обработчиках событий невидимы для границы.
onClick,onChange— это не render-фаза. Ловите их локальноtry/catchи отражайте в state:const [error, setError] = useState(null). - Logginng может сам упасть.
componentDidCatchв production должен быть устойчив к ошибкам сервиса логирования — оберните вtry/catchили убедитесь, что Sentry/Datadog SDK не бросает. - Пары Suspense + ErrorBoundary обязательны. Rejected-промис от
use()ловится Error Boundary. Без неё в паре с Suspense — пустой экран без объяснений. Всегда держите ErrorBoundary рядом с Suspense. - SSR-стриминг удваивает логирование. При ошибке ниже Suspense-границы в Next.js стриминге сервер и клиент могут оба зафиксировать одну ошибку. Дедублируйте в Sentry через fingerprint или фильтрацию по environment.
- Кнопка «Попробовать снова» требует сброса state. Boundary перейдёт обратно в нормальный режим только если её state
errorсбросить. Вreact-error-boundaryэто делаетresetErrorBoundary(); в самописной реализации нужно явно вызыватьsetState({ error: undefined }). - Не путайте с
window.onerrorиunhandledrejection. Error Boundary видит контекст React-дерева (component stack, props). Глобальные обработчики видят все ошибки JS, включая асинхронные. Они дополняют друг друга — используйте оба. - В dev-mode поведение отличается. React 18+ в development кратковременно показывает overlay со стек-трейсом перед тем, как передать ошибку в boundary. В production overlay нет. Не удивляйтесь, если в dev всё «мигает» даже при корректно работающей границе.
Common mistakes
- Ожидать, что граница поймает ошибку в
onClick. - Делать одну границу на всё приложение.
- Класть в fallback компонент, который сам может упасть.
What the interviewer is testing
- Знает ли, какие ошибки ловятся, а какие нет.
- Понимает ли отсутствие хука и причины этого.
- Применяет ли границы вместе с Suspense.