I can make a class an error boundary in React by implementing componentDidCatch
.
Is there a clean approach to making a functional component into an error boundary without converting it into a class?
Or is this a code smell?
asked Jan 28, 2018 at 2:29
4
As of v16.2.0, there’s no way to turn a functional component into an error boundary.
The React docs are clear about that, although you’re free to reuse them as many times as you wish:
The
componentDidCatch()
method works like a JavaScriptcatch {}
block, but for components. Only class components can be error boundaries. In practice, most of the time you’ll want to declare an error boundary component once and use it throughout your application.
Also bear in mind that try/catch
blocks won’t work on all cases.
If a component deep in the hierarchy tries to updates and fails, the try/catch
block in one of the parents won’t work — because it isn’t necessarily updating together with the child.
answered Jan 28, 2018 at 9:04
gustavohenkegustavohenke
40.5k14 gold badges117 silver badges125 bronze badges
There is an implementation that can handle with non-existent functionalities for a functional component such as componentDidCatch
and deriveStateFromError
.
According to the author, it is based on React.memo().
The proposed solution is greatly inspired by the new React.memo() API.
import Catch from "./functional-error-boundary"
type Props = {
children: React.ReactNode
}
const MyErrorBoundary = Catch(function MyErrorBoundary(props: Props, error?: Error) {
if (error) {
return (
<div className="error-screen">
<h2>An error has occured</h2>
<h4>{error.message}</h4>
</div>
)
} else {
return <React.Fragment>{props.children}</React.Fragment>
}
})
reference and API here
answered Feb 17, 2020 at 2:44
Luis FebroLuis Febro
1,6631 gold badge14 silver badges21 bronze badges
As mentioned already, the React team has not yet implemented a hook equivalent, and there are no published timelines for a hook implementation.
A few third party packages on npm implement error boundary hooks. I published react-use-error-boundary, attempting to recreate an API similar to useErrorBoundary from Preact:
import { withErrorBoundary, useErrorBoundary } from "react-use-error-boundary";
const App = withErrorBoundary(({ children }) => {
const [error, resetError] = useErrorBoundary(
// You can optionally log the error to an error reporting service
(error, errorInfo) => logErrorToMyService(error, errorInfo)
);
if (error) {
return (
<div>
<p>{error.message}</p>
<button onClick={resetError}>Try again</button>
</div>
);
}
return <div>{children}</div>;
});
answered Jun 21, 2021 at 23:23
Tate ThurstonTate Thurston
4,0191 gold badge25 silver badges20 bronze badges
Official React team not provided Error boundary support for functional component.
We can achieve error boundary for functional component using npm package.
https://www.npmjs.com/package/react-error-boundary
answered Mar 30, 2021 at 12:48
SujitSujit
5916 silver badges25 bronze badges
What I have done is create custom class component
and wrapped my functional/class
component inside it where ever its required. This is how my custom class component looks like:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {error: ""};
}
componentDidCatch(error) {
this.setState({error: `${error.name}: ${error.message}`});
}
render() {
const {error} = this.state;
if (error) {
return (
<div>{error}</div>
);
} else {
return <>{this.props.children}</>;
}
}
}
And used it like this my functional/class
components:
<ErrorBoundary key={uniqueKey}>
<FuncationalOrChildComponent {...props} />
</ErrorBoundary>
PS: As usual the key property is very important as it will make sure to re-render the ErrorBoundary
component if you have dynamic child elements.
answered Dec 1, 2021 at 4:39
DipakDipak
6,3947 gold badges63 silver badges86 bronze badges
Представим, что у нас есть приложение на React, в котором можно читать и писать отзывы. Пользователь открыл список отзывов, пролистал его, нажал кнопку «Написать отзыв». Форма написания отзыва открывается в попапе, над списком. Пользователь начинает вводить текст, свой e-mail. Вдруг валидация почты срабатывает с ошибкой, которую разработчики забыли обработать. Результат — белый экран. React просто не смог ничего отрендерить из-за этой ошибки в каком-то попапе.
Первая же мысль — не надо было всё уничтожать, список же был не при делах. Чтобы обработать ошибку в render-фазе в React, необходимо использовать Error Boundaries. Почему именно так нужно обрабатывать ошибки — расскажу под катом.
try/catch спешит на помощь
Итак, начнём с простого. Если попросить вас обработать ошибки в JavaScript-коде, вы без сомнений обернете код в конструкцию try/catch:
try {
throw new Error('Привет, Мир! Я ошибка!');
} catch (error) {
console.error(error);
}
Запустим его и, как ни удивительно, в консоли мы увидим текст ошибки и стек вызовов. Всем известная конструкция, на рынке JavaScript с 1995 года. Код достаточно прост в понимании. Всё работает идеально.
Теперь обратим свой взор на React. Разработчики данной библиотеки позиционируют её как простую функцию, которая принимает на вход любые данные и возвращает визуальное представление этих данных:
function React(data) {
return UI;
}
const UI = React({ name: 'John' });
Выглядит несколько абстрактно, но пока нам этого хватит. Кажется, тут можно применить паттерн обработки ошибок в JavaScript, к которому мы так уже привыкли:
try {
const UI = React({ name: 'John' });
} catch (error) {
console.error(error);
}
Пока всё выглядит достаточно логично и просто. Попробуем реализовать подобный подход в реальном коде.
Обернём все в try/catch
Любое React-приложение начинается с того, что мы рендерим самый верхнеуровневый компонент — точку входа в приложение — в DOM-ноду:
ReactDOM.render(
<App />,
document.getElementById("root")
);
Старый добрый синхронный рендер <App /> и всех компонентов внутри. Отличное место, чтобы обернуть приложение в try/catch:
try {
ReactDOM.render(
<App />,
document.getElementById("root")
);
} catch (error) {
console.error("React render error: ", error);
}
Ошибки, которые будут брошены во время первого рендера, будут пойманы этим try/catch.
Но если ошибка будет происходить в результате, например, смены стейта какого-либо компонента внутри, то этот try/catch уже не сработает, так как свою функцию ReactDOM.render уже выполнит к тому моменту — то есть выполнит первоначальный рендер <App /> в DOM. Всё, что будет происходить дальше, его не касается.
Вот есть демо, где можно поиграться с таким try/catch. В AppWithImmediateError.js находится компонент, который бросает ошибку при первом же рендере. В AppWithDeferredError.js — после изменения внутреннего стейта. Как видно, catch ловит ошибку только из AppWithImmediateError.js (см. консоль).
Что-то мы не видели, что так обрабатывают ошибки в компонентах в React. Этот пример я привёл просто для иллюстрации того, как работает первый рендер в браузере, когда мы только выполняем рендер приложения в реальный DOM. Дальше ещё будет несколько странных примеров, но они раскроют некоторые особенности в работе React.
Кстати, в новых методах первого рендера в React 18 не будет синхронной версии рендера всего приложения сразу. Поэтому такой подход с оборачиванием в try/catch не будет работать даже для первого рендера.
try/catch внутри компонента
Просто сделать глобальный try/catch — интересная затея. Только вот она не работает. Может тогда просто внутри рендера в <App /> сделать try/catch? Прям в рендер запихать. И ведь нет никаких запретов на это. Опустим тут вопрос про декларативность, чистоту функций. Не будем разбрасываться терминами — всё же синтаксис позволяет такой пируэт:
// Можно и классовый компонент взять
// и внутри render() try/catch написать.
// Разницы нет
const App = () => {
try {
return (
<div>
<ChildWithError />
</div>
);
} catch (error) {
console.error('App error handler: ', error);
return <FallbackUI/>;
}
}
Сделал демку для такого варианта. Открываем, тыкаем в кнопку Increase value. Когда value достигнет значения 4, <ChildWithError/> кинет ошибку в render-функции. Но ни сообщения в консоли, ни FallbackUI нет. Как же так? Мы же знаем, что
<div>
<ChildWithError />
</div>
в результате транспиляции (babel’ем, typescript’ом, кем-то ещё, кого вы выбрали) превращается в
React.createElement(
'div',
null,
React.createElement(ChildWithError, null)
)
Вот тут можно поиграться с babel’ем, например.
То есть весь наш JSX превращается в вызовы функций. Таким образом, try/catch должен был отловить ошибку. В чём тут подвох? Неужели React умеет останавливать вызов функции?
С чем на самом деле работает React
Если приглядеться, то мы видим, что в React.createElement(ChildWithError, null) нет вызова рендера ChildWithError. Погодите, а что вообще возвращает вызов React.createElement? Если кому-то интересно прям source потыкать, то вот ссылка на то место, где создаётся элемент. В общем виде возвращается вот такой объект:
// Исходник: https://github.com/facebook/react/blob/main/packages/react/src/ReactElement.js#L148
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE, // Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props, // Record the component responsible for creating this element.
_owner: owner,
};
На выходе render-функции мы получаем объекты, которые вложены в другие объекты. Для нашего примера мы получим объект, который описывает <div>, у которого в props.children будет лежать объект, который описывает <ChildWithError />. Попробуйте сами вывести в консоль результат вызова render-функции.
Прямого вызова render-функции ChildWithError мы не видим внутри <App />. Мы лишь создали схему, по которой в дальнейшем будет рендерится <App />.
Render выполняется от родителей к детям. После рендера <App /> внутри <ChildWithError /> тоже создаётся объект, который описывает все элементы, возвращаемые render-функцией компонента <ChildWithError />. Мы как бы говорим React’у: если отренедрился <App />, то внутри него потом надо отрендерить <ChildWithError />.
Кстати, в этом и заключается декларативность React’а. А не в том, что мы цикл for написать в теле render-функции не можем.
И тут вы можете воскликнуть — чтобы объект такой собрать, нужно же функцию ChildWithError вызвать? Всё верно, только вызов функции ChildWithError происходит не внутри <App />. Он происходит совсем в другом месте. Пока что можно ограничиться таким объяснением — React сам вызывает render-функции компонентов в каком-то своём контексте. Позже я раскрою эту мысль глубже. В конкурентом режиме (он ждёт нас во всей красе в React 18) React ещё и во времени может эти вызовы раскидать так, как сам посчитает нужным.
Приведу аналогию такую: componentDidUpdate не происходит же в контексте рендера, он просто запускается React’ом после того, как компонент перерендерился. Либо вот такая аналогия (которая даже ближе к истине):
try {
Promise.resolve().then(() => {
throw new Error('wow!');
});
} catch (error) {
console.log('Error from catch: ', error);
}
Ошибка из промиса никогда не будет поймана в catch, так как происходит в другом месте Event-loop’а. Catch — синхронный callstack задач, промис — микротаска.
В том, что React сам вызывает render-функции компонентов, легко убедиться. Достаточно в демке поменять <ChildWithError /> на {ChildWithError()} внутри <App />. Мы прям руками сами вызовем render-функцию компонента ChildWithError внутри рендера <App />. Ошибка начнёт обрабатываться с помощью try/catch. Увидим сообщение в консоли и даже fallback UI отрендерится!
И почему бы везде так не писать? Просто делать вызов функции-компонента. Должно же и работать быстрее, не придётся ждать, когда там и где React запустит рендер детей.
Тут я сразу отправлю читать замечательную статью Дэна Абрамова React as a UI Runtime. Конкретно про вызов компонента внутри рендера другого компонента можно прочитать в разделе Inversion of Control и далее Lazy Evaluation.
Забавно, но какое-то время назад ручной вызов рендера компонентов предлагался как паттерн по ускорению работы React-приложений. Вот пример, когда такой вызов может даже в ногу выстрелить:
function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount(c => c + 1)
return <button onClick={increment}>{count}</button>
}
function App() {
const [items, setItems] = React.useState([])
const addItem = () => setItems(i => [...i, {id: i.length}])
return (
<div>
<button onClick={addItem}>Add Item</button>
<div>{items.map(Counter)}</div>
</div>
)
}
Попробуйте поиграться с этим примером в codesandbox. Уже после первого нажатия на AddItem мы получим ошибку, что в разных рендерах были вызваны разные хуки. А это нарушает правило использования хуков в React. Оставлю ссылочку на статью Kent C. Dodds про этот пример.
Хорошо, что ребята из Facebook занимаются просветительской деятельностью. И тут не только про Дэна речь. у них есть замечательный канал на YouTube — Facebook Open Source. Особенно нравятся их ролики в формате Explain Like I’m 5. Крайне рекомендую, чтобы самому научиться просто объяснять сложные штуки. Вот один из таких примеров:
Но у нас тут не такой формат — чуть более многословный.
Вернёмся к обработке ошибок. Простого try/catch внутри render() {} будет мало! А как же componentDidUpdate и другие lifecycle-методы? Да-да, классовые компоненты ещё поддерживаются в React. Если в функциональном компоненте мы просто вообще всё обернули бы в try/catch (опустим вопрос здравого смысла такого подхода), то в классовом компоненте придётся в каждый lifecycle-метод пихать try/catch. Не очень изящно, да… Какой вывод? Правильно, переходим на функциональные компоненты! Там try/catch юзать проще =)
Ладно, закончим играться с try/catch. Кажется, мы поняли, что в React мы не достигнем успеха с ним. Но, прежде чем переходить к Error Boundaries, я покажу ещё одну демку, которая точно отобьёт любое желание использовать try/catch для отлова ошибок рендера.
Сферический пример в вакууме
Что тут у нас есть: функциональный компонент <App />, у которого определён внутренний стейт. Значение из этого стейта шарится по всему дочернему дереву через React.context. <App /> рендерит внутри себя компонент <Child />. <Child /> обернут в HOC memo, внутри себя рендерит компонент <GrandChild />.
Внутри <Child /> я использовал классический try/catch, чтобы поймать все ошибки в рендере ниже по дереву. Внутри <GrandChild /> есть логика, что если значение из контекста будет больше 3, то бросается ошибка в рендере. Схематично это всё выглядит примерно так:
В <App /> используются getDerivedStateFromError, чтобы обновить стейт компонента <App /> в случае ошибки в дочернем дереве. Также есть componentDidCatch, в котором можно выполнить любой side effect в случае ошибки в дочернем дереве. То есть <App /> выступает в этом приложении как Error Boundary — именно он является той самой границей, за которую ошибка из дочернего дерева не пролезет дальше, вверх по дереву.
Зачем всё так сложно? Да потому что!
Берём и тыкаем в кнопку. Как видим, после первого клика перендерился только <App /> и <GrandChild />. <App /> — потому что у него стейт поменялся, <GrandChild /> — потому что поменялось значение в контексте. <Child /> же вообще никак не участвовал в этом процессе, так как он обернут в HOC memo. Как будто его и нет, хотя он находится, если так можно сказать, между <App /> и <GrandChild />. Подсветим зелёным тех ребят, кто перерендерился в этой ситуации.
Продолжая увеличивать счётчик в <App>, мы дойдём до ошибки в <GrandChild />. Как и в прошлые разы, при увеличении счётчика <Child /> вновь не будет участвовать, а значит try/catch внутри него тоже не сработает.
Эта демка — простая модель, которая отражает, что React сам решает, когда и что отрендерить и в каком контексте.
Вот мы и увидели, как работает Error Boundaries. Как пользоваться Error Boundaries и какие у него есть ограничения, я описывать не буду. Есть ссылка на доку на Reactjs.org, там всё достаточно подробно описали. Кстати, там указано, а когда всё же try/catch можно использовать. Да, мы от него полностью не отказываемся
Но куда интереснее понять, как именно это работает в React. Это что, какой-то особый try/catch?
try/catch по-React’овски
В игру вступает магический React Fiber. Это и название архитектуры, и название сущности из внутренностей React. Информация об этой сущности открывается для широкой общественности с приходом React 16. Вот, даже в официальной доке засветился.
Кто смотрел результат вызова React.createElement в консоль, тот видел, что там выводится гораздо больше информации, нежели чем ожидается. На скрине можно увидеть только часть из того, что React туда кладёт:
Суть вот в чём: помимо дерева React-элементов/компонентов, существует ещё и набор неких Fiber-нод, которые к этим элементам/компонентам привязаны. В этих нодах содержится внутреннее состояние (полезное только для React) React-элемента/компонента: какие были пропсы ранее, какой следующий effect запустить, нужно ли рендерить компонент сейчас и т. д.
Подробно про Fiber-архитектуру можно почитать в блоге inDepth.dev или в статье от одного из ключевых разработчиков из React-core — acdlite.
Имея это внутреннее представление, React уже знает, что делать с ошибкой, которая случилась во время фазы рендера конкретного компонента. То есть React может остановить рендер текущего дерева компонентов, отыскать ближайший сверху компонент, в котором есть или getDerivedStateFromError, или componentDidCatch — хотя бы кто-то один. Имея ссылки на родителей в FiberNode (там в связном списке всё лежит), сделать это проще простого. Вот есть source-код, в котором можно посмотреть, как это примерно работает.
Например, вот функция, в которой определяется, имеет ли компонент методы Error Boundaries. А вот исходник того, как организован внутренний так называемый workLoop в React. Тут же можно понять, что никакой магии в React нет — внутри всё же используется старый добрый try/catch для отлова ошибок. Почти как в нашем абстрактном примере, который я привел в начале статьи.
Для классического React с синхронным рендером используется тот же подход. Просто функция для workLoop используется другая. Впрочем, конкурентный React (18 версия и более новые) — это совсем другая история. Рекомендую открыть ссылки и поизучать их отдельно после прочтения этой статьи.
В общем виде это выглядит так:
-
Запускаем рендер компонента.
-
Если во время workLoop была ошибка, она будет поймана в try/catch и обработана.
-
В списке FiberNode ищем компонент-родитель с необходимыми методами (getDerivedStateFromError или componentDidCatch).
-
Нашли — React считает ошибку обработанной.
-
Все ветки отрендеренного дерева можно не выбрасывать. Отбросим только ту ветку, где была ошибка — ровно до того места, где мы определили Error Boundaries, границы распространения этой ошибки.
Если можно было бы представить работу с React, как с живым человеком, то общение с ним выглядело бы как-то так (своего рода объяснение в стиле Explain Like I’m 5):
Привет, я — React.
Спасибо за инструкции в JSX о том, что куда рендерить.
Дальше я сам все буду делать. Можешь расслабиться)
try {
*React изображает бурную деятельность*
} catch (error) {
Ну вот, опять ошибка.
Пойду искать родителей этого негодяя, который ошибку бросил.
Может они хоть что-то с ошибкой этой сделают.
Ну а то, что я уже сделал у других родителей — выбрасывать не буду.
Зря работал что ли?
}
The message
Такое копание в реализации какой-либо фичи порой дает интересные результаты. Можно иначе взглянуть на давно уже знакомую библиотеку/фреймворк. Или просто лучше понять, как использовать свой инструмент по-максимуму. Рекомендую всем иногда погружаться в какой-либо аспект в реализации вашего любимого JS-решения. Я точно уверен, что это путешествие окупится.
Список литературы
Да-да, прям как в рефератах) Ссылок много, хочется, чтобы к ним легко можно было вернуться:
-
Error Boundaries, документация React. Описание того, как обработать ошибки у вас в приложении
-
Статья Дэна Абрамова React as a UI Runtime. Дэн достаточно глубоко описывает то, как работает React.
-
Статья Kent C. Dodds Don’t call a React function component. Наглядное объяснение, почему не стоит самому вызывать render-компоненты.
-
Facebook Open Source. Youtube-канал разработчиков из Facebook.
-
Inside Fiber: in-depth overview of the new reconciliation algorithm in React. Достаточно хардкорная статья про новую архитектуру React. На этом же ресурсе есть еще одна замечательная статья: The how and why on React’s usage of linked list in Fiber to walk the component’s tree. В общем, это для тех, кто хочет прям глубже погрузиться в то, как устроен React изнутри.
-
React Fiber Architecture. Чуть менее хардкорное описание Fiber-архитектуры.
Thanks to React hooks you have now happily turned all your classes into functional components.
Wait, all your components? Not quite. There is one thing that can still only be implemented using classes: Error boundaries.
There is just no functional equivalent for componentDidCatch
and deriveStateFromError
yet.
Proposed solution
The proposed solution is greatly inspired by the new React.memo()
API.
import Catch from "./functional-error-boundary" type Props = { children: React.ReactNode } const MyErrorBoundary = Catch(function MyErrorBoundary(props: Props, error?: Error) { if (error) { return ( <div className="error-screen"> <h2>An error has occured</h2> <h4>{error.message}</h4> </div> ) } else { return <React.Fragment>{props.children}</React.Fragment> } })
API
type ErrorHandler = (error: Error, info: React.ErrorInfo) => void
Catch(component: React.ComponentType)
Catch(component: React.ComponentType, errorHandler: ErrorHandler)
Wraps the functional component component
to make it an error boundary.
The caught error is passed to the component
function as a second parameter.
The optional second argument to Catch()
(errorHandler
) is a function that acts as componentDidCatch()
. Use it to handle side effects like error logging.
Implementation
Find the code below.
Outlook
Would be great to see something like this integrated into the React.js core library:
import React from "react" type Props = { children: React.ReactNode } const MyErrorBoundary = React.Catch(function MyErrorBoundary(props: Props, error?: Error) { if (error) { return ( <div className="error-screen"> <h2>An error has occured</h2> <h4>{error.message}</h4> </div> ) } else { return <React.Fragment>{props.children}</React.Fragment> } })
Whenever an error occurs and an exception is thrown in a React application, there is a strong possibility that the application display no longer works and that the user will only see a blank page. To avoid this behavior, React introduced so-called Error Boundaries in version 16.0.
An Error Boundary describes a component which can catch certain errors in its children and can also render an alternative component tree to protect users from experiencing a blank page. Error Boundaries always serve as a parent component of a component tree. If an exception is thrown in the component tree, the Error Boundary can intercept and handle the error. Try and think of error boundaries as a special form of a try / catch
block for component hierarchies.
They can deal with mistakes that result from the handling of the following situations:
-
Errors in lifecycle methods
-
Errors in the
render()
method anywhere inside the Error Boundary -
Errors in the
constructor()
of a component
If React encounters an error in a lifecycle method, the render()
method or in the constructor of a component, the Error Boundary can safely prevent it. It can display a fallback that can prompt the user to restart their application or inform them that something has gone wrong. Similar to Context components, Error Boundaries can be nested inside each other. If an error occurs, the implementation of the higher Error Boundary component takes precedence.
Attention: Error Boundaries‘ primary goal is to prevent and deal with errors in the handling of user interfaces which would otherwise prevent further rendering of the application status. If you think about implementing form validation with Error Boundaries, please refrain from doing so as Error Boundaries were not intended for this use case and should not be used for that matter.
There are certain situations in which Error Boundaries do not work:
-
in asynchronous code (like
setTimeOut()
orrequestAnimationFrame()
) -
in server-side rendered components (SSR)
-
in errors which occur in the Error Boundary itself
Error Boundaries will not work in these situations as it is either not necessary or not possible for them to deal with the problem at hand. If an event-handler throws an error, this might not necessarily impact its render and React can continue to show a working interface to the user. The only repercussion would be the missing interaction based on said event.
Implementing an Error Boundary
Implementing an Error Boundary
There are two simple rules when it comes to implementing an Error Boundary:
-
1.
Only Class components can be turned into an Error Boundary
-
2.
The class has to implement the static
getDerivedStateFromError()
method or the class methodcomponentDidCatch()
(or both of them)
Strictly speaking, we are already dealing with an Error Boundary from a technical point of view if one or both of the methods mentioned above have been implemented. All other rules that apply to regular Class components also apply to Error Boundaries.
Let’s look at an implementation of an Error Boundary:
class ErrorBoundary extends React.Component {
static getDerivedStateFromError(error) {
componentDidCatch(error, info) {
console.log(error, info);
if (this.state.hasError) {
return <h1>An error has occured.</h1>;
return this.props.children;
First of all, we define a new component. We have named this component ErrorBoundary
but it is possible to give it any other name too. You can freely choose the name of the Error Boundary and only need to adhere to React’s component naming conventions: components need to start with a capital letter and be a valid JavaScript function name.
For matters of simplicity and readability, I would urge you to choose clear and identifiable component names such asAppErrorBoundary
or DataTableErrorFallback
. This will allow other team members in your project to see which components are used to deal with errors at a glance.
In the above example we have set up state with a property of hasError
and provided an initial value of false
as errors usually do not occur during initialization.
Next, let’s look at the static getDerivedStateFromError()
method. Using this method, React is informed that the component in use is supposed to act as an Error Boundary and should come into effect if an error occurs in its children. The method itself is passed an error
object which is the same as the object which is also passed to the catch
block of the try / catch
statement.
getDerivedStateFromError()
works very similar to the getDerivedStateFromProps()
method we have already encountered in the chapter on lifecycle methods. It can return a new object and thus create new state or leave all as is by returning null
. In the above example, we have set the hasError
property to true
and also save the error
object in our state. As the method itself is static though, it cannot access other methods in the component.
This method is called during the render()
phase of a component when React compares the current component tree with its previous version and just before the changes are committed to the DOM.
The componentDidCatch()
method has also been implemented. It receives an error object as its first parameter and React-specific information as its second. This information contains the «Component Stack» — crucial information which allows us to trace in which components we have encountered errors and more specifically how which children and children of children were involved. It will display the component tree up until an error will occur. If you want to use an external service to log these errors, this method is a good place to deal with side effects. componentDidCatch()
is run during the Commit phase meaning just after React has displayed changes from state in the DOM.
As componentDidCatch()
is not a static method, it would be entirely possible to modify its state via this.setState()
. However, the React Team plans to prohibit this usage in the future which is why I do not recommend it at this point. It is safer to use the static getDerivedStateFromError()
method instead to create a new state and react to errors once they have occurred.
Finally, we react to possible errors in the render()
method. If the hasError
property in state is set to true
, we know that an error has occurred and can thus display a warning such as <h1>An error occured.</h1>
. If on the other hand everything works as expected, we simply return this.props.children
. How exactly the errors encountered are dealt with is up to the developer. For example, it might be sufficient to inform the user that certain information cannot be displayed at the moment if the error is only small. If however serious errors have been encountered, we should prompt the user to reload the application.
Error Boundaries in practice
Error Boundaries in practice
We now know how to implement an Error Boundary: by adding either static getDerivedStateFromError()
or componentDidCatch()
to your components. Error Boundaries should not implement their own logic, should not be too tightly coupled to other components and be as independent as possible. It is at the developer’s discretion to decide how granular the Error Boundary should be according to the specific use case.
It’s a good idea to implement different and nested Error Boundaries to cater to a variety of errors: one Error Boundary that wraps around the whole application, as well as one that wraps only optional components in the component tree. Let’s look at another example:
import React from ‘react’;
import ReactDOM from ‘react-dom’;
<ServiceUnavailableBoundary>
</ServiceUnavailableBoundary>
ReactDOM.render(<App />, document.querySelector(‘#root’));
Two Error Boundaries are used in the above example: ErrorBoundary
and ServiceUnavailableBoundary
. While the outer boundary will catch errors that might occur in the ApplicationLogic
component, the ServiceUnavailableBoundary
could catch errors in the weather widget and display a more granular error message like «the service requested cannot be reached at the moment. Please try again later».
If the WeatherWidget
component throws an error, the ServiceUnavailableBoundary
will catch it and everything that is currently used in the ApplicationLogic
component will remain intact. If we did not include the WeatherWidget
in its own Error Boundary, the outer Error Boundary would be used instead and the ApplicationLogic
component would not be shown.
Generally, it is good practice to have at least one Error Boundary as high up as possible in the component hierarchy. This will catch most unexpected errors like a 500 Internal Server Error
page would do and can also log them. If needed, further Error Boundaries should be added to encompass useful logic in further component trees. This depends entirely on how error prone a specific area of the tree is (due to unknown or changing data) or if a specific area of the tree has been neglected.
Since React version 16, components will be «unmounted» and removed from the tree if a serious error occurred or an exception was thrown. This is important as it ensures that the user interface does not suddenly stop working or returns incorrect data. It is especially critical to ensure if we were to work with online banking data. Imagine the consequences if we were to incorrectly send money to the wrong recipient or transfer an incorrect amount.
In order to deal with these errors and risks properly, Error Boundaries were introduced. They allow developers to inform users that the application is currently in an erroneous state. As errors and mistakes can never be fully avoided in an application, using Error Boundaries is highly recommended.
When you are writing a react application you have two ways to handling errors:
- Using try/catch block in each component
- Using React Error Boundary which is only available in class Component
import * as React from 'react'
import ReactDOM from 'react-dom'
function City({name}) {
return <div>Hello, visit {name.toUpperCase()}</div>
}
function Country({capital}) {
return <div>Hello, visit {capital.toUpperCase()}</div>
}
function App() {
return (
<div>
<Country />
<City />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Enter fullscreen mode
Exit fullscreen mode
The above piece of code would end up showing you an error page when you run it in Development or a blank screen in Production.
Obviously, the error we created in the code above could have certainly been handled with PropTypes or TypeScript, however we are aware runtime error happens all the time and we are going to deal with it using the two approaches stated above.
Try/catch
import * as React from 'react'
import ReactDOM from 'react-dom'
function ErrorHandler({error}) {
return (
<div role="alert">
<p>An error occurred:</p>
<pre>{error.message}</pre>
</div>
)
}
function City({name}) {
try {
return <div>Hello, visit {name.toUpperCase()}</div>
} catch (error) {
return <ErrorHandler error={error} />
}
}
function Country({capital}) {
try {
return <div>Hello, visit {capital.toUpperCase()}</div>
} catch (error) {
return <ErrorHandler error={error} />
}
}
function App() {
return (
<div>
<Country />
<City />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Enter fullscreen mode
Exit fullscreen mode
This approach, requires us to define an ErrorHandler component to display in case an error occurs and we wrap each component returned element in the try/catch block.
This seems ok, but repetitive. What if we want the parent component to handle the error catching for us. Wrapping the parent component App
in a try/catch block will not work, due to the nature of how React calls functions. That is when React Error Boundary comes in.
React Error Boundary
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
As at React 17.0.2, Error Boundary works only in
- Class component
- and It must implement
static getDerivedStateFromError()
orcomponentDidCatch()
In order to use Error Boundary in Functional Component, I use react-error-boundary.
import * as React from 'react'
import ReactDOM from 'react-dom'
import {ErrorBoundary} from 'react-error-boundary'
function ErrorHandler({error}) {
return (
<div role="alert">
<p>An error occurred:</p>
<pre>{error.message}</pre>
</div>
)
}
function City({name}) {
return <div>Hello, visit {name.toUpperCase()}</div>
}
function Country({capital}) {
return <div>Hello, visit {capital.toUpperCase()}</div>
}
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorHandler}>
<Country />
<City />
</ErrorBoundary>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Enter fullscreen mode
Exit fullscreen mode
When we run this application, we will get a nice error display form the content of ErrorHandler
component.
React error boundary catches any error from the components below them in the tree. This is really handy and useful because we need not declare a separate try/catch for each component because the wrapping component(ErrorBoundary) takes care of that and display the component of the FallbackComponent
provided.
Exceptions to Error handling
Because react-error-boundary uses react error boundary in the background there are a few exceptions to the errors that can be handled.
These errors are not handled by react-error-boundary
- Event handlers
- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
- Server side rendering
- Errors thrown in the error boundary itself (rather than its children)
Error recovery
This library offers an error recovery feature, that allow you to reset the state and bring back the components to a working point.
Let’s use this example from the react-error-boundary npmjs page.
function ErrorFallback({error, resetErrorBoundary}) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}
function Bomb() {
throw new Error('💥 CABOOM 💥')
}
function App() {
const [explode, setExplode] = React.useState(false)
return (
<div>
<button onClick={() => setExplode(e => !e)}>toggle explode</button>
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => setExplode(false)}
resetKeys={[explode]}
>
{explode ? <Bomb /> : null}
</ErrorBoundary>
</div>
)
}
Enter fullscreen mode
Exit fullscreen mode
The ErrorBoundary
component accepts two other props to help recover from a state of error. The first prop onReset
receives a function which will be triggered when resetErrorBoundary
of the FallbackComponent
is called. The onReset
function is used to reset the state and perform any cleanup that will bring the component to a working state.
The other prop of ErrorBoundary
is resetKeys
, it accepts an array of elements that will be checked when an error has been caught. In case any of these elements changes, the ErrorBoundary
will reset the state and re-render the component.
Handling error in React functional components should be a breeze for anyone using the react-error-boundary
library. It provides the following features:
- Fallback components to display incase of error
- Granular capturing of error at component level
- Recovery of error using a function or by resetting the elements causing the component to fail.
Errors are an inevitable part of development. They always seem to pop up at the most inopportune times to make sure our apps don’t work as intended. Regardless of how they find their way into our code, error handling is the antidote to these unintentional dev snafus, better known as exceptions.
In regular JavaScript, the try…catch
statement offers an API with which we can try and gracefully catch potentially error-prone code:
try { console.log(p) } catch (error) { console.log(`Error: ${error}`) // ReferenceError: p is not defined }
Any error detected in the try
block is thrown as an exception and caught in the catch
block, keeping our applications more resilient to errors. In React, this construct works fine for handling JavaScript errors as below, wherein we’re fetching data with useEffect
:
useEffect(() => { try { fetchUsers(); } catch(error) { setError(error); } }, []);
But this doesn’t work so well in React components.
Why try...catch
doesn’t catch JavaScript errors in React components
It’s wrong to think that by virtue of what it does and how it works that the try...catch
statement can be used to catch JavaScript errors in React components. This is because the try...catch
statement only works for imperative code, as opposed to the declarative code we have in components.
function ErrorFallbackUI({ errorMessage }) { return ( <div className="article-error"> <h3>There was a problem displaying the article:</h3> <h3 className="error">{errorMessage}</h3> </div> ); } // There's an attempt to uppercase each publication title function Publication({ publication }) { return ( <a href={publication.href} className="article-cta"> <h3 className="title">{publication.title.toUpperCase()}</h3> <h3 className="info">{publication.lead}</h3> </a> ); } // Map over the list of publications and try to render a <Publication/> function Publications({ publications }) { try { return publications.map((publication, index) => ( <div key={index}> <Publication {...{ publication }} /> </div> )); } catch (error) { return <ErrorFallbackUI {...{ errorMessage: error.errorMessage }} />; } }
It’s important to note that the errors above will only show up in development. In production, the UI will be corrupted, and the user will be served a blank screen. How can we solve for this? Enter error boundaries.
N.B., tools like create-react-app and Next.js have an error overlay component that blocks the UI whenever there is an error, overshadowing your own error boundaries. In create-react-app, which this tutorial uses, we need to dismiss or close the overlay with the X mark in the upper right-hand corner to view our own error boundaries in use.
Using error boundaries in React
Error boundaries are React components that offer a way to gracefully handle JavaScript errors in React components. With them, we can catch JavaScript runtime errors in our components, act on those errors, and display a fallback UI.
import ErrorBoundary from "error-boundary"; function Users() { return ( <div> <ErrorBoundary> {/* the rest of your application */} </ErrorBoundary/> </div> ) }
Error boundaries operate like the catch
block in the JavaScript try...catch
statement with a couple exceptions: they are declarative and, perhaps needless to say, only catch errors in React components.
More specifically, they catch errors in their child component(s), during the rendering phase, and in lifecycle methods. Errors thrown in any of the child components of an error boundary will be delegated to the closest error boundary in the component tree.
In contrast, error boundaries do not catch errors for, or that occur in:
- Event handlers
- Asynchronous code
- Server-side rendering
- Errors thrown in the error boundary itself
There are a few rules to follow for a component to act as an error boundary:
- It must be a class component
- It must define one or both of the lifecycle methods
static getDerivedStateFromError()
orcomponentDidCatch()
The rule of thumb is that static getDerivedStateFromError()
should be used to render a fallback UI after an error has been thrown, while componentDidCatch()
should be used to log those error(s).
The <PublicationErrorBoundary/>
component below is an error boundary since it satisfies the necessary criteria:
class PublicationErrorBoundary extends Component { state = { error: false, errorMessage: '' }; static getDerivedStateFromError(error) { // Update state to render the fallback UI return { error: true, errorMessage: error.toString() }; } componentDidCatch(error, errorInfo) { // Log error to an error reporting service like Sentry console.log({ error, errorInfo }); } render() { const { error, errorMessage } = this.state; const { children } = this.props; return error ? <ErrorFallbackUI {...{ error, errorMessage }} /> : children; } }
The <PublicationErrorBoundary/>
replaces the try...catch
block from the <Publications/>
component above with the following update:
function Publications({ publications }) { return publications.map((publication, index) => ( <div key={index}> <PublicationErrorBoundary> <Publication {...{ publication }} /> </PublicationErrorBoundary> </div> )); }
Now, the error thrown from <Publication/>
trying to uppercase each publication title allows for graceful exception handling. The error has been isolated and a fallback UI displayed.
You can see this example on CodeSandbox.
Where to place error boundaries in the component tree
The Next.js documentation does a good job of explaining the general rule of thumb: error boundaries shouldn’t be too granular; they are used by React in production and should always be designed intentionally.
Error boundaries are special React components and should be used to catch errors only where appropriate. Different error boundaries can be used in different parts of an application to handle contextual errors, though they can be generic — for example, a network connection error boundary.
Notice that in the prior example, the <PublicationErrorBoundary/>
is used in Publications
instead of Publication
. If used otherwise, the error thrown from the attempt to uppercase each publication title won’t be caught by the <PublicationErrorBoundary/>
because the children of <PublicationErrorBoundary/>
will be a hyperlink and not a component. However, using it in <Publications/>
solves for this.
// This won't work because the children of `PublicationErrorBoundary` // aren't components function Publication({ publication }) { return ( <PublicationErrorBoundary> <a href={publication.href} className="article-cta"> <h3 className="title">{publication.title.toUpperCase()}</h3> <h3 className="info">{publication.lead}</h3> </a> </PublicationErrorBoundary> ); }
Also, notice that each publication has an error boundary listening for it to err. What if it makes sense to display no publications at all if there’s an error with at least one of them? <Publications/>
can be updated:
// Before function Publications({ publications }) { return publications.map((publication, index) => ( <div key={index}> <PublicationErrorBoundary> <Publication {...{ publication }} /> </PublicationErrorBoundary> </div> )); } // After. Notice the location of `PublicationErrorBoundary` function Publications({ publications }) { return ( <PublicationErrorBoundary> {publications.map((publication, index) => ( <div key={index}> <Publication {...{ publication }} /> </div> ))} </PublicationErrorBoundary> ); }
The UI will be updated to show the fallback UI:
Error boundary propagation
Similarly to how the catch
block works, if an error boundary fails trying to render the error message, the error will propagate to the closest error boundary above it. If errors thrown are not caught by any of the error boundaries, the whole component tree will be unmounted.
This is a new behavior as of React 16, with the argument being that it is better to completely remove a corrupted UI than to leave it in place, and that it provides a better user experience in the event of an error.
<AppErrorBoundary> <App> <PublicationsErrorBoundary> <Publications> <PublicationErrorBoundary> <Publication /> <PublicationErrorBoundary> </Publications> </PublicationsErrorBoundary> </App> </AppErrorBoundary>
Take the contrived component tree above as an example. If an error is caught in <Publication/>
, the closest error boundary, <PublicationErrorBoundary/>
, will be responsible for it. In the event that it fails in its responsibility, <PublicationsErrorBoundary/>
will act as a fallback. If that fails as well, <AppErrorBoundary/>
will attempt to handle the error before the component is completely unmounted.
Resetting error boundaries
This is one of the edge cases I find in using error boundaries. Typically, this is done by resetting the UI state and recovering the UI from corruption.
Say a user loses connection, and a connection error boundary has succeeded in displaying a fallback UI alerting the user to check their connection. Can we recover the UI when the connection state becomes active? The simple answer is yes, and the other is that it depends.
Errors can be tricky, dynamic, and non-deterministic, and it is better you look into data fetching tools like SWR or React Query, or the react-error-boundary package, which alleviates these kinds of problems.
Conclusion
Wherever it is rational to do so, you should be using error boundaries to handle runtime errors in your React applications. The more familiar you get with them, the more you understand the reason for their existence.
With the react-error-boundary package specifically, you can reduce the amount of redundant code and uncertainty you encounter with a well-tested abstraction — which, as Kent Dodds puts it, is the last error boundary component anyone needs.
LogRocket: Full visibility into your production React apps
Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time,
try LogRocket.
LogRocket
combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?
Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.
No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps —
start monitoring for free.
Предохранители¶
Ранее ошибки JavaScript внутри компонентов портили внутреннее состояние React и заставляли его выдавать таинственные сообщения об ошибках во время следующего рендера. Эти сообщения всегда вызывались ошибками, расположенными где-то выше в коде приложения, но React не предоставлял способа адекватно обрабатывать их в компонентах и не мог обработать их самостоятельно.
Представляем предохранители (компоненты Error Boundary)¶
Ошибка JavaScript где-то в коде UI не должна прерывать работу всего приложения. Чтобы исправить эту проблему для React-пользователей, React 16 вводит концепцию «предохранителя» (error boundary).
Предохранители это компоненты React, которые отлавливают ошибки JavaScript в любом месте деревьев их дочерних компонентов, сохраняют их в журнале ошибок и выводят запасной UI вместо рухнувшего дерева компонентов. Предохранители отлавливают ошибки при рендеринге, в методах жизненного цикла и конструкторах деревьев компонентов, расположенных под ними.
Примечание
Предохранители не поймают ошибки в:
- обработчиках событий (подробнее);
- асинхронном коде (например колбэках из
setTimeout
илиrequestAnimationFrame
);- серверном рендеринге (Server-side rendering);
- самом предохранителе (а не в его дочерних компонентах).
Классовый компонент является предохранителем, если он включает хотя бы один из следующих методов жизненного цикла: static getDerivedStateFromError()
или componentDidCatch()
. Используйте static getDerivedStateFromError()
при рендеринге запасного UI в случае отлова ошибки. Используйте componentDidCatch()
при написании кода для журналирования информации об отловленной ошибке.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
// Обновить состояние с тем, чтобы следующий рендер показал запасной UI.
return { hasError: true }
}
componentDidCatch(error, info) {
// Можно также сохранить информацию об ошибке в соответствующую службу журнала ошибок
logErrorToMyService(error, info)
}
render() {
if (this.state.hasError) {
// Можно отрендерить запасной UI произвольного вида
return <h1>Что-то пошло не так.</h1>
}
return this.props.children
}
}
И можно дальше им пользоваться, как обыкновенным компонентом:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
Предохранители работают как JavaScript-блоки catch {}
, но только для компонентов. Только классовые компоненты могут выступать в роли предохранителей. На практике чаще всего целесообразным будет один раз описать предохранитель и дальше использовать его по всему приложению.
Обратите внимание, что предохранители отлавливают ошибки исключительно в своих дочерних компонентах. Предохранитель не сможет отловить ошибку внутри самого себя. Если предохранителю не удаётся отрендерить сообщение об ошибке, то ошибка всплывает до ближайшего предохранителя, расположенного над ним в дереве компонентов. Этот аспект их поведения тоже напоминает работу блоков catch {}
в JavaScript.
Живой пример¶
Посмотрите пример объявления и использования предохранителя в React 16.
Где размещать предохранители¶
Степень охвата кода предохранителями остаётся на ваше усмотрение. Например, вы можете защитить им навигационные (route) компоненты верхнего уровня, чтобы выводить пользователю сообщение «Что-то пошло не так», как это часто делают при обработке ошибок серверные фреймворки. Или вы можете охватить индивидуальными предохранителями отдельные виджеты, чтобы помешать им уронить всё приложение.
Новое поведение при обработке неотловленных ошибок¶
Это изменение влечёт за собой существенное последствие. Начиная с React 16, ошибки, не отловленные ни одним из предохранителей, будут приводить к размонтированию всего дерева компонентов React.
Хотя принятие этого решения и вызвало споры, судя нашему опыту, бо́льшим злом будет вывести некорректный UI, чем удалить его целиком. К примеру, в приложении типа Messenger, вывод поломанного UI может привести к тому, что пользователь отправит сообщение не тому адресату. Аналогично, будет хуже, если приложение для проведения платежей выведет пользователю неправильную сумму платежа, чем если оно не выведет вообще ничего.
Это изменение означает, что при миграции на React 16 вы с большой вероятностью натолкнётесь на незамеченные ранее ошибки в вашем приложении. Добавляя в ваше приложение предохранители, вы обеспечиваете лучший опыт взаимодействия с приложением при возникновении ошибок.
Например, Facebook Messenger охватывает содержимое боковой и информационной панелей, журнала и поля ввода сообщений отдельными предохранителями. Если один из этих компонентов UI упадёт, то остальные сохранят интерактивность.
Также мы призываем пользоваться сервисами обработки ошибок JavaScript (или написать собственный аналогичный сервис), чтобы вы знали и могли устранять необработанные исключения в продакшен-режиме.
Стек вызовов компонентов¶
В режиме разработки React 16 выводит на консоль сообщения обо всех ошибках, возникших при рендеринге, даже если они никак не сказались на работе приложения. Помимо сообщения об ошибке и стека JavaScript, React 16 также выводит и стек вызовов компонентов. Теперь вы в точности можете видеть в каком именно месте дерева компонентов случилось страшное:
Кроме этого, в стеке вызовов компонентов выводятся имена файлов и номера строк. Такое поведение по умолчанию настроено в проектах, созданных при помощи Create React App:
Если вы не пользуетесь Create React App, вы можете вручную добавить к вашей конфигурации Babel вот этот плагин. Обратите внимание, что он предназначен исключительно для режима разработки и должен быть отключён в продакшен.
Примечание
Имена компонентов, выводимые в их стеке вызовов, определяются свойством
Function.name
. Если ваше приложение поддерживает более старые браузеры и устройства, которые могут ещё не предоставлять его нативно (например, IE 11), рассмотрите возможность включения полифиллаFunction.name
в бандл вашего приложения, напримерfunction.name-polyfill
. В качестве альтернативы, вы можете явным образом задать пропdisplayName
в каждом из ваших компонентов.
А как насчёт try/catch?¶
try
/ catch
— отличная конструкция, но она работает исключительно в императивном коде:
try {
showButton()
} catch (error) {
// ...
}
В то время, как компоненты React являются декларативными, указывая что должно быть отрендерено:
Предохранители сохраняют декларативную природу React и ведут себя так, как вы уже привыкли ожидать от компонентов React. Например, если ошибка, произошедшая в методе componentDidUpdate
, будет вызвана setState
где-то в глубине дерева компонентов, она всё равно корректно всплывёт к ближайшему предохранителю.
А что насчёт обработчиков событий?¶
Предохранители не отлавливают ошибки, произошедшие в обработчиках событий.
React не нуждается в предохранителях, чтобы корректно обработать ошибки в обработчиках событий. В отличие от метода render
и методов жизненного цикла, обработчики событий не выполняются во время рендеринга. Таким образом, даже если они сгенерируют ошибку, React всё равно знает, что нужно выводить на экран.
Чтобы отловить ошибку в обработчике событий, пользуйтесь обычной JavaScript-конструкцией try
/ catch
:
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { error: null }
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
try {
// Делаем что-то, что сгенерирует ошибку
} catch (error) {
this.setState({ error })
}
}
render() {
if (this.state.error) {
return <h1>Отловил ошибку.</h1>
}
return (
<div onClick={this.handleClick}>Нажми на меня</div>
)
}
}
Обратите внимание, что приведённый выше пример демонстрирует стандартное поведение JavaScript и не использует предохранителей.
Изменение названия метода по сравнению с React 15¶
React 15 включал очень ограниченную поддержку предохранителей с другим названием метода: unstable_handleError
. Этот метод больше не работает и вам будет нужно заменить его в вашем коде на componentDidCatch
начиная с первого бета-релиза React 16.
Для обработки этого изменения мы написали codemod, обеспечивающий автоматическую миграцию вашего кода.