Давайте признаем, что тот, кто ищет что-то в интернете, не хочет наткнуться на нерабочую пустую страницу. Это, как минимум, путает, сбивает с толку. Сидишь и не понимаешь, что произошло и почему, это оставляет плохое впечатление о сайте. Часто лучше сообщить об ошибке и дать пользователю продолжить пользоваться приложением или сайтом. В таком случае он получит меньше негативных эмоций и не закроет это приложение.
В этой статье мы пройдёмся по различным способам справиться с ошибками в приложениях на React.

Классический метод «Try and Catch» в React
Если вы использовали JavaScript, вам, вероятно, приходилось писать инструкцию «try and catch». Чтобы убедиться в этом, посмотрите:
try {
somethingBadMightHappen();
} catch (error) {
console.error("Something bad happened");
console.error(error);
Это отличный инструмент для выявления неправильного кода и обеспечения того, чтобы наше приложение не сломалось. Чтобы быть более реалистичным и максимально приближенным к миру React, давайте посмотрим пример того, как вы будете использовать это в своем приложении:
const fetchData = async () => {
try {
return await fetch("https://some-url-that-might-fail.com");
} catch (error) {
console.error(error); // You might send an exception to your error tracker like AppSignal
return error;
}
При выполнении сетевых вызовов в React обычно используют инструкцию try...catch. Но почему? К сожалению, try...catch работает только с императивным кодом, но не работает с декларативным, таким как JSX, который пишут в компонентах. Вот почему вы не видите массивной упаковки try...catch всего нашего приложения. Это просто не сработает.
Итак, что делать? В React 16 появилась новая концепция — границы ошибок React. Давайте разберемся, что это такое.
Границы ошибок React
Прежде чем мы перейдем к границам ошибок, давайте сначала посмотрим, почему они необходимы. Представьте, что у вас есть такой компонент:
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>;
};
export default CrashableComponent
Если вы попытаетесь отобразить этот компонент где-нибудь, вы получите ошибку, подобную этой:

Мало того, вся страница будет пустой, и пользователь не сможет ничего делать или видеть. Но что произошло? Мы попытались получить доступ к свойству iDontExist.prop, которого не существует (мы не передаем его компоненту). Это банальный пример, но он показывает, что мы не можем поймать эти ошибки try...catch с помощью инструкции.
Весь этот эксперимент подводит нас к границам ошибок. Границы ошибок — это компоненты React, которые улавливают ошибки JavaScript в любом месте своего дочернего дерева компонентов. Затем они регистрируют эти обнаруженные ошибки и отображают резервный пользовательский интерфейс вместо дерева компонентов, которое разбилось. Границы ошибок улавливают ошибки во время рендеринга, в методах жизненного цикла и в конструкторах всего дерева под ними.
Граница ошибки — это классовый компонент, который определяет один (или оба) из методов жизненного цикла static getDerivedStateFromError() или componentDidCatch(). static getDerivedStateFromError() отображает резервный пользовательский интерфейс после возникновения ошибки. componentDidCatch() можно передавать информацию об ошибках вашему поставщику услуг (например, AppSignal) или в консоль браузера.
Вот пример того, как информация об ошибке React выглядит в «списке проблем» AppSignal:

Давайте посмотрим на типичный компонент границы ошибки:
import { Component } from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return (
<div>
<p>Something went wrong ????</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary
Мы можем использовать ErrorBoundary примерно так:
<ErrorBoundary>
<CrashableComponent />
</ErrorBoundary
Теперь, когда мы открываем наше приложение, мы получим рабочее приложение с текстом:

Это именно то, чего мы хотим. Мы хотим, чтобы наше приложение оставалось функциональным при возникновении ошибки. Но также хотим информировать пользователя (и нашу службу отслеживания ошибок) об ошибке.
Помните, что использование границ ошибок не панацея. Границы ошибок не распознают ошибки для:
-
Обработчики событий.
-
Асинхронный код (например,
setTimeout, илиrequestAnimationFrameCallbacks). -
Server-side rendering.
-
Ошибки, которые возникают в самой границе ошибки (а не в ее дочерних элементах).
В этих случаях всё равно нужно использовать try...catch. И так, давайте продолжим и покажем, как вы можете это сделать.
Перехват ошибок в обработчиках событий
Как упоминалось ранее, границы ошибок не могут нам помочь, когда ошибка выдается внутри обработчика событий. Давайте посмотрим, как можно с ними справиться. Ниже приведен небольшой компонент кнопки, который выдает ошибку при нажатии на него:
import { useState } from "react";
const CrashableButton = () => {
const [error, setError] = useState(null);
const handleClick = () => {
try {
throw Error("Oh no :(");
} catch (error) {
setError(error);
}
};
if (error) {
return <span>Caught an error.</span>;
}
return <button onClick={handleClick}>Click Me To Throw Error</button>;
};
export default CrashableButton
Обратите внимание, что у нас есть блок try and catch внутри handleClick, который гарантирует, что наша ошибка будет обнаружена. Если вы отобразите компонент и попытаетесь щелкнуть по нему, это произойдет:

Нужно делать то же самое в других случаях, например, в вызовах setTimeout
Перехват ошибок в вызовах setTimeout
Представьте, что у нас есть аналогичный компонент button, но он вызывает setTimeout при нажатии. Вот как это выглядит:
import { useState } from "react";
const SetTimeoutButton = () => {
const [error, setError] = useState(null);
const handleClick = () => {
setTimeout(() => {
try {
throw Error("Oh no, an error :(");
} catch (error) {
setError(error);
}
}, 1000);
};
if (error) {
return <span>Caught a delayed error.</span>;
}
return (
<button onClick={handleClick}>Click Me To Throw a Delayed Error</button>
);
};
export default SetTimeoutButton
Через 1000 миллисекунд callback setTimeout выдаст ошибку. К счастью, мы включаем эту логику обратного вызова в try...catch и в компонент setError. Таким образом, трассировка стека не отображается в консоли браузера. Кроме того, мы сообщаем об ошибке пользователю. Вот как это выглядит в приложении:

Таким образом, мы запустили страницы приложения, несмотря на то, что ошибки появляются повсюду в фоновом режиме. Но есть ли более простой способ обработки ошибок без написания пользовательских границ ошибок? Вы можете поспорить, что есть, и, конечно же, он поставляется в виде пакета JavaScript. Позвольте мне познакомить вас с react-error-boundary.
react-error-boundary пакет JavaScript
Вы можете вставить эту библиотеку в свой package.json быстрее, чем когда-либо, с:
npm install --save react-error-boundary
Теперь вы готовы использовать его. Помните компонент ErrorBoundary, который мы создали? Вы можете забыть об этом, потому что этот пакет экспортирует свои собственные. Вот как это использовать:
import { ErrorBoundary } from "react-error-boundary";
import CrashableComponent from "./CrashableComponent";
const FancyDependencyErrorHandling = () => {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error) => {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
console.error(error);
}}
>
<CrashableComponent />
</ErrorBoundary>
);
};
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong ????</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
export default FancyDependencyErrorHandling
В этом примере визуализируем то же CrashableComponent, но на этот раз мы используем компонент ErrorBoundary из библиотеки react-error-boundary. Он делает то же самое, что и наш пользовательский, за исключением того, что он получает FallbackComponent и обработчик функции onError. Результат тот же, что и с нашим пользовательским компонентом ErrorBoundary, за исключением того, что вам не нужно беспокоиться о его обслуживании, поскольку вы используете внешний пакет.
Одна из замечательных особенностей этого пакета заключается в том, что вы можете легко обернуть свои функциональные компоненты в компонент withErrorBoundary более высокого порядка (HOC). Вот как это выглядит:
import { withErrorBoundary } from "react-error-boundary";
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>;
};
export default withErrorBoundary(CrashableComponent, {
FallbackComponent: () => <span>Oh no :(</span>,
});
Хорошо, теперь вы можете записывать все те ошибки, которые вас беспокоят.
Но, возможно, вы не хотите, чтобы в вашем проекте была другая зависимость. Давайте посмотрим, как это можно сделать самостоятельно.
Используя свои собственные границы React
Похожего, если не точно такого же эффекта можно достичь с помощью react-error-boundary. Мы уже разбирали кастомный ErrorBoundary компонент, но предлагаю его улучшить.
import { Component } from "react";
export default class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />;
}
return this.props.children;
}
}
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong ????</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
const errorBoundary = (WrappedComponent) => {
return class extends ErrorBoundary {
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />;
}
return <WrappedComponent {...this.props} />;
}
};
};
export { errorBoundary };
У вас получились ErrorBoundary и HOC errorBoundary, которые вы можете использовать во всем приложении. Их можно масштабировать и видоизменять. Вы можете сделать так, чтобы они получали индивидуальные fallback компоненты для кастомизации способов восстановления после каждой ошибки. Ещё можно настроить получение onError, и потом вызывать его внутриcomponentDidCatch. Возможности не ограничены.
Одно могу сказать точно — эти взаимосвязи не нужны в конце концов. Уверен, написание собственного error boundary даст ощущение успеха, сможете лучше их понимать. Ну и кто знает, может, придут какие-то интересные идеи в голову, пока вы экспериментируете с кастомизацией.
Резюмируем:
-
Границы ошибок React отлично подходят для обнаружения ошибок в декларативном коде (например, внутри дерева дочерних компонентов).
-
Для других случаев необходимо использовать инструкцию
try...catch(например, асинхронные вызовы, такие какsetTimeoutобработчики событий, рендеринг на стороне сервера и ошибки, возникающие в самой границе ошибки). -
Подобная библиотека
react-error-boundaryпомогает писать меньше кода. -
Вы также можете запустить свою собственную границу ошибок и настроить ее так, как хотите.
Let’s face it. Nobody wants to see a broken, empty page while surfing the web. It leaves you stranded and confused. You don’t know what happened or what caused it, leaving you with a bad impression of the website.
It is often better to communicate the error and let the user continue to use the app. The user will get less of a bad impression and can continue to use its features.
In today’s post, we’ll go through different ways to handle errors in React applications.
The Classic ‘Try and Catch’ Method in React
If you’ve used JavaScript, you’ve probably had to write a ‘try and catch’ statement. To make sure we’re on board with what it is, here’s one:
try {
somethingBadMightHappen();
} catch (error) {
console.error("Something bad happened");
console.error(error);
}
It is a great tool to catch misbehaving code and ensure our app doesn’t blow up into smithereens. To be more realistic and close to the React world as possible, let’s see an example of how you’d use this in your app:
const fetchData = async () => {
try {
return await fetch("https://some-url-that-might-fail.com");
} catch (error) {
console.error(error); // You might send an exception to your error tracker like AppSignal
return error;
}
};
When doing network calls in React, you’d usually use the try...catch statement. But why? Unfortunately, try...catch only works on imperative code. It does not work on declarative code like the JSX we are writing in our components. So that is why you don’t see a massive try...catch wrapping our whole app. It just won’t work.
So, what do we do? Glad you asked. In React 16, a new concept got introduced — React Error Boundaries. Let’s dig into what they are.
React Error Boundaries
Before we get into error boundaries, let us first see why they are necessary. Imagine you had a component like this:
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>;
};
export default CrashableComponent;
If you try to render this component somewhere, you’ll get an error like this one:

Not only that, the whole page will be blank, and the user won’t be able to do or see anything. But what happened? We tried to access a property iDontExist.prop, which doesn’t exist (we don’t pass it to the component). This is a banal example, but it shows that we cannot catch these errors with the try...catch statement.
This whole experiment brings us to error boundaries. Error boundaries are React components that catch JavaScript errors anywhere in their child component tree. Then, they log those caught 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.
An error boundary is a class component that defines either (or both) of the lifecycle methods static getDerivedStateFromError() or componentDidCatch().
static getDerivedStateFromError() renders a fallback UI after an error has been thrown.
componentDidCatch() can log error information to your service provider (like AppSignal) or to a browser console.
Here’s an example of how information about a React error looks in AppSignal’s ‘issue list’:

Let’s see a typical error boundary component:
import { Component } from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
We can use ErrorBoundary like so:
<ErrorBoundary>
<CrashableComponent />
</ErrorBoundary>
Now, when we open our app, we will get a working app with the following:

That is precisely what we want. We want our app to remain functional when an error occurs. But we also want to inform the user (and our error tracking service) about the error.
Beware that using an error boundary is not a silver bullet. Error boundaries do not catch errors for:
- Event handlers
- Asynchronous code (e.g.
setTimeoutorrequestAnimationFramecallbacks) - Server-side rendering
- Errors that are thrown in the error boundary itself (rather than its children)
You still need to use the try...catch statement for these fellas. So, let’s go ahead and show how you can do that.
Error Catching in Event Handlers
As mentioned before, error boundaries can’t help us when an error is thrown inside an event handler. Let’s see how we can handle those. Below is a small button component that throws an error when you click it:
import { useState } from "react";
const CrashableButton = () => {
const [error, setError] = useState(null);
const handleClick = () => {
try {
throw Error("Oh no :(");
} catch (error) {
setError(error);
}
};
if (error) {
return <span>Caught an error.</span>;
}
return <button onClick={handleClick}>Click Me To Throw Error</button>;
};
export default CrashableButton;
Notice that we have a try and catch block inside handleClick that ensures our error is caught. If you render the component and try to click it, this happens:

We have to do the same in other cases, like in setTimeout calls.
Error Catching in setTimeout Calls
Imagine we have a similar button component, but this one calls setTimeout when clicked. Here’s how it looks:
import { useState } from "react";
const SetTimeoutButton = () => {
const [error, setError] = useState(null);
const handleClick = () => {
setTimeout(() => {
try {
throw Error("Oh no, an error :(");
} catch (error) {
setError(error);
}
}, 1000);
};
if (error) {
return <span>Caught a delayed error.</span>;
}
return (
<button onClick={handleClick}>Click Me To Throw a Delayed Error</button>
);
};
export default SetTimeoutButton;
After 1,000 milliseconds, the setTimeout callback will throw an error. Luckily, we wrap that callback logic in try...catch, and setError in the component. That way, no stack trace is shown in the browser console. Also, we communicate the error to the user. Here’s how it looks in the app:

That is all well and good, as we got our app’s pages up and running despite errors popping all over the place in the background. But is there an easier way to handle errors without writing custom error boundaries? You bet there is, and of course, it comes in the form of a JavaScript package. Let me introduce you to the react-error-boundary.
JavaScript’s react-error-boundary Package
You can pop that library inside your package.json faster than ever with:
npm install --save react-error-boundary
Now, you’re ready to use it. Remember the ErrorBoundary component we made? You can forget about it because this package exports its own. Here’s how to use it:
import { ErrorBoundary } from "react-error-boundary";
import CrashableComponent from "./CrashableComponent";
const FancyDependencyErrorHandling = () => {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error) => {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
console.error(error);
}}
>
<CrashableComponent />
</ErrorBoundary>
);
};
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
export default FancyDependencyErrorHandling;
In the example above, we render the same CrashableComponent, but this time, we use the ErrorBoundary component from the react-error-boundary library. It does the same thing as our custom one, except that it receives the FallbackComponent prop plus the onError function handler. The result is the same as we had with our custom ErrorBoundary component, except you don’t have to worry about maintaining it since you’re using an external package.
One great thing about this package is that you can easily wrap your function components into a withErrorBoundary making it a higher-order component (HOC). Here’s how that looks:
import { withErrorBoundary } from "react-error-boundary";
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>;
};
export default withErrorBoundary(CrashableComponent, {
FallbackComponent: () => <span>Oh no :(</span>,
});
Nice, you’re good to go now to capture all those errors bugging you.
But maybe you don’t want another dependency in your project. Can you achieve it yourself? Of course you can. Let’s see how it can be done.
Using Your Own React Boundaries
You can achieve a similar, if not the same, effect you get from react-error-boundary. We already showed a custom ErrorBoundary component, but let’s improve it.
import { Component } from "react";
export default class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />;
}
return this.props.children;
}
}
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong 😭</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
const errorBoundary = (WrappedComponent) => {
return class extends ErrorBoundary {
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />;
}
return <WrappedComponent {...this.props} />;
}
};
};
export { errorBoundary };
Now you get the ErrorBoundary and the HOC errorBoundary that you can use across your app. Extend and play around with it as much as you want. You can make them receive custom fallback components to customize how you recover from each error. You can also make them receive an onError prop and later call it inside componentDidCatch. The possibilities are endless.
But one thing is for sure — you didn’t need that dependency after all. I bet writing your own error boundary will bring a sense of achievement, and you’ll get to understand it better. Also, who knows what ideas you might get when you’re trying to customize it.
Summing Up: Get Started with React Error Handling
Thanks for reading this blog post about handling errors in React. I hope you had as much fun reading and trying things out as I did writing it. You can find all the code, with examples, in the GitHub repo I created.
A quick rundown of the things we went through:
- React Error boundaries are great for catching errors in declarative code (e.g., inside their child component tree).
- For other cases, you need to use a
try...catchstatement (e.g., async calls likesetTimeout, event handlers, server-side rendering, and errors thrown in the error boundary itself). - A library like
react-error-boundarycan help you write less code. - You can also run your own error boundary and customize it as much as you want.
That is all, folks. Thanks for tuning in, and catch you in the next one!
P.S. If you liked this post, subscribe to our JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.
P.P.S. If you need an APM for your Node.js app, go and check out the AppSignal APM for Node.js.

Разработчики фронтенд-приложений часто не уделяют должного внимания работе над ошибками и их регистрации (logging). Однако появление ошибок в любом фрагменте кода требует тщательного изучения. Для этого в React есть несколько способов, применяемых в зависимости от ситуации. Рассмотрим их подробнее.
Лучшие методы обработки ошибок
Надежная обработка ошибок необходима для нормальной работы приложений. Вот несколько подходящих методик.
1. Обработка ошибок в компонентах класса с помощью граничного метода
Error Boundaries (граничный метод) — это самый простой и эффективный способ обработки ошибок в компонентах React.
В этом случае можно создать компонент границ ошибки, включив метод жизненного цикла componentDidCatch (errorinfo). Лучше всего использовать эту функцию для регистрации информации об ошибках в сервисе регистрации ошибок (logging service).
Кроме того, можно использовать статическую функцию getDerivedStateFromError(error) для обновления состояния и метод render() для отображения резервного пользовательского интерфейса (fallback UI) в случае возникновения ошибки.
Пример компонента границ ошибки.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
} static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Зарегистрируйте ошибку в сервисе отчета об ошибках
errorService.log({ error, errorInfo });
}
render() {
if (this.state.hasError) {
return <h1>Oops, something went wrong.</h1>;
}
return this.props.children;
}
}
После этого нужно лишь заключить требуемый компонент(ы) внутри компонента границ ошибки.
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
Степень детализации границ ошибки определяет пользователь. Например, можно заключить в границы ошибки компоненты маршрута верхнего уровня, чтобы показать пользователю страницу с общей ошибкой, или отдельные виджеты.
Возникшая за границами ошибка отразится на всем дереве компонентов React и вызовет сбой приложения.
Поэтому убедитесь в том, что ненадежные компоненты заключены в компонент границ ошибки. Хотя границы и являются отличным способом борьбы с ошибками, у них есть несколько недостатков.
Например, метод жизненного цикла componentDidCatch(error, info) вызывается только в том случае, если ошибка возникает при рендеринге, в методах жизненного цикла или в конструкторах любой дочерней составляющей компонента.
В результате границы ошибок не работают в обработчиках событий, асинхронных обратных вызовах или при рендеринге на стороне сервера.
2. Обработка ошибок с помощью Try-Catch: перехват за пределами границ
Блоки try-catch — не лучший способ обработки исключений на уровне компонентов, потому что они не перехватывают каскадно переданные из дочерних компонентов исключения.
Однако try-catch используются для обработки ошибок, которые не улавливаются границами.
Рассмотрим сценарий регистрации пользователя с асинхронным вызовом API. Для обработки возникающих ошибок здесь можно использовать try-catch и состояния React.
function SignUpButton(props) {
const [hasError, setError] = React.useState(false);
const handleClick = async () => {
try {
await api.signUp();
} catch(error) {
errorService.log(error)
setError(true);
}
}
if (hasError) {
return <p>Sorry, Sign up failed!</p>;
}
return <button onClick={handleClick}>Sign up</button>;
}
Аналогично try-catch можно использовать в ситуациях с неэффективными границами ошибок. Однако в некоторых случаях это может привести к дублированию кода.
3. Библиотека react-error-boundary
Библиотека react-error-boundary значительно упрощает в React обработку ошибок. Она эффективно расширяет базовые возможности error boundaries.
Библиотека позволяет отображать резервный компонент, регистрировать ошибки так же, как в базовых error boundaries, и сбрасывает состояние приложения, чтобы ошибка снова не повторялась.
import {ErrorBoundary} from 'react-error-boundary'function MyFallbackComponent({error, resetErrorBoundary}) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}
export default function App() {
return (
<ErrorBoundary
FallbackComponent={MyFallbackComponent}
onError={(error, errorInfo) => errorService.log({ error, errorInfo })}
onReset={() => {
// Сброс состояния приложения
}}
>
<MyErrorProneComponent />
</ErrorBoundary>
);
}
Более того, теперь с помощью пользовательского хука
useErrorHandler()можно обрабатывать ошибки в обработчиках событий и асинхронном коде.
Если хук use ErrorHandler (error) вызван со значением ошибки truthy, то react-error-boundary распространит это значение до ближайшей границы ошибки.
Поэтому react-error-boundary заменяет два разных методах обработки ошибок в приложении.
Лучшие методики регистрации ошибок
Многие разработчики знают о существовании журналов консоли, но далеко не все понимают, как нужно постоянно регистрировать ошибки в приложении React. Журналы консоли помогают в обнаружении корневых (root) ошибок при разработке, но они становятся неэффективными для приложения, выполняемого в браузере пользователя.
Поэтому необходимо создавать серверную службу регистрации ошибок или стороннюю службу их отслеживания.
Кроме того, анализ журнала возможно позволит разобраться во взаимодействии пользователей с приложением. Это полезно для распознавания их поведения и угроз безопасности.
Например, множественные неудачные попытки входа в систему могут свидетельствовать о желании получить несанкционированный доступ к приложению.
Инструменты
Для постоянной регистрации ошибок можно использовать сторонние сервисы регистрации на стороне клиента. Ниже приведены одни из лучших провайдеров.
Sentry
Sentry предоставляет настраиваемый компонент границы ошибок для React, автоматически отправляющий ошибки JavaScript внутри дерева компонентов в Sentry. Его можно использовать аналогично базовому компоненту границ ошибки React.
redux-logger
redux-logger также известен как Logger for Redux. Он позволяет пользователю создать свой собственный регистратор с настраиваемыми параметрами. Он прост в использовании и позволяет настраивать ведение журнала для консоли и уровня производительности.
Log level
Log level заменяет стандартный console.log(), имеет функции ведения журнала и фильтрации на основе иерархии уровней, которые предоставляют гораздо больше контроля над журналами. В сравнении с Sentry или redux-logger, у него меньше функций, но есть основные, чаще всего необходимые.
Можно использовать loglevel-plugin-remote для отправки журналов на удаленный сервер для хранения, анализа и предупреждений. Журналы следует отправлять в виде объектов JSON, а не обычным текстом, чтобы их можно было легко сортировать и упорядочить.
Заключение
В любом качественном программном продукте необходимо предусмотреть обработку ошибок и непредвиденных событий. Грамотно выполненные обработка и регистрация ошибок помогут в выявлении причин их возникновения, оказывая при этом минимальное влияние на работу пользователя.
Читайте также:
- 7 основных навыков, необходимых для фронтенд-разработчика в 2021 году
- Подробно о перечислениях в Typescript
- Введение в фронтэнд и бэкэнд – основы веб-разработки
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Manusha Chethiyawardhana: React Error Handling and Logging Best Practices
API calls may return errors, learn how to deal with them
In React we often have to work with external APIs. We typically grab data from a remote server and we display it in our application.
We saw that one very popular library for making http requests to remote servers is Axios. Axios lets us use methods like get(), post(), and others that call the corresponding http methods that deal with getting, posting, updating and deleting data from an API.
One good place where we want to put Axios calls is inside the componentDidMount() function of our class components.
componentDidMount() gets called by React automatically when the component mounts in our application. If we place the call to Axios in there, it will be called at the appropriate moment and the data retrieved will be available to the component state, ready to be displayed.
Possible API errors
Not all calls to external APIs are successful, though. In fact, it’s very possible that a remote server is down or some other blockage prevents the data we are looking for to be accessed.
In these cases, Axios will return an error. It’s common practice to notify the user that an error has occurred by triggering some kind of notification like displaying an error message in our web page.
How do we display error messages?
Let’s say we want to display an error message at the top of our view when something bad happens. In order to display the message we need to have the message sitting ready in our component state.
Let’s add an errorMessage property to our state object with the value of an empty string as the initial state.
state = {
items: [],
errorMessage: ''
}
We place our Axios call inside componentDidMount() and when the call is successful, we set the state to the value returned in the API response.
componentDidMount() {
axios.get('http://localhost:3333/items')
.then(response => this.setState({items: response.data}))
.catch(err => { console.log(err) })
}
But when there is an error, the data won’t be available inside then(), and the catch() method will be called instead. The error object returned by the API will be passed in there.
At this point, what we need to do is grab the error and update the errorMessage property in our state using setState().
In the code below, I show this operation. In the catch branch I call setState() with an object that updates errorMessage with whatever error is returned by the API.
componentDidMount() {
axios.get('http://localhost:3333/items')
.then(response => this.setState({items: response.data}))
.catch(err => {
this.setState({errorMessage: err.message});
})
}
Now that we have the error in our state all we have to do is display it at the
top of our web page. How do we do that?
Display the error
There are many ways to do it but we like to create a conditional statement to
display the error. The conditional statement basically needs to say:
«if we have an errorMessage on the state, display an h3 element with the errorMessage value. However, if errorMessage is empty, don’t display anything.»
To translate this if condition into code we could use a plain old if statement, but we can also use a fancy way of doing it.
We use the shortcut operator &&.
The && operator is placed in the middle of a statement.
- It first evaluates the left side of the statement.
- If the left side is true, then the right side of the statement is executed.
- If the left side is not true,
&&will not do anything with the right side.
In the code below we use the && operator to display the error message only if the errorMessage property on the state is not empty:
{ this.state.errorMessage &&
<h3 className="error"> { this.state.errorMessage } </h3> }
This is saying: if this.state.errorMessage is true, display the error message.
Remember, we need to enclose this statement in brackets because we are writing Javascript code inside JSX.
In summary
- API calls to external resources can get stuck and return errors instead of the expected data.
- In this case we catch the error and we display it in our application, so the user knows something went wrong.
- We display the error using a conditional statement that shows the error only if it exists.
As you can see, it’s very easy to write code that display error messages inside our React application.
I write daily about web development. If you like this article, feel free to share it with your friends and colleagues.
You can receive articles like this in your inbox by subscribing to my newsletter.
| id | title | permalink |
|---|---|---|
|
error-boundaries |
Предохранители |
docs/error-boundaries.html |
Ранее ошибки JavaScript внутри компонентов портили внутреннее состояние React и заставляли его выдавать таинственные сообщения об ошибках во время следующего рендера. Эти сообщения всегда вызывались ошибками, расположенными где-то выше в коде приложения, но React не предоставлял способа адекватно обрабатывать их в компонентах и не мог обработать их самостоятельно.
Представляем предохранители (компоненты Error Boundary) {#introducing-error-boundaries}
Ошибка 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, errorInfo) {
// Можно также сохранить информацию об ошибке в соответствующую службу журнала ошибок
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// Можно отрендерить запасной UI произвольного вида
return <h1>Что-то пошло не так.</h1>;
}
return this.props.children;
}
}
И можно дальше им пользоваться, как обыкновенным компонентом:
<ErrorBoundary> <MyWidget /> </ErrorBoundary>
Предохранители работают как JavaScript-блоки catch {}, но только для компонентов. Только классовые компоненты могут выступать в роли предохранителей. На практике чаще всего целесообразным будет один раз описать предохранитель и дальше использовать его по всему приложению.
Обратите внимание, что предохранители отлавливают ошибки исключительно в своих дочерних компонентах. Предохранитель не сможет отловить ошибку внутри самого себя. Если предохранителю не удаётся отрендерить сообщение об ошибке, то ошибка всплывает до ближайшего предохранителя, расположенного над ним в дереве компонентов. Этот аспект их поведения тоже напоминает работу блоков catch {} в JavaScript.
Живой пример {#live-demo}
Посмотрите пример объявления и использования предохранителя.
Где размещать предохранители {#where-to-place-error-boundaries}
Степень охвата кода предохранителями остаётся на ваше усмотрение. Например, вы можете защитить им навигационные (route) компоненты верхнего уровня, чтобы выводить пользователю сообщение «Что-то пошло не так», как это часто делают при обработке ошибок серверные фреймворки. Или вы можете охватить индивидуальными предохранителями отдельные виджеты, чтобы помешать им уронить всё приложение.
Новое поведение при обработке неотловленных ошибок {#new-behavior-for-uncaught-errors}
Это изменение влечёт за собой существенное последствие. Начиная с React 16, ошибки, не отловленные ни одним из предохранителей, будут приводить к размонтированию всего дерева компонентов React.
Хотя принятие этого решения и вызвало споры, судя по нашему опыту, бо́льшим злом будет вывести некорректный UI, чем удалить его целиком. К примеру, в приложении типа Messenger, вывод поломанного UI может привести к тому, что пользователь отправит сообщение не тому адресату. Аналогично, будет хуже, если приложение для проведения платежей выведет пользователю неправильную сумму платежа, чем если оно не выведет вообще ничего.
Это изменение означает, что при миграции на React 16 вы с большой вероятностью натолкнётесь на незамеченные ранее ошибки в вашем приложении. Добавляя в ваше приложение предохранители, вы обеспечиваете лучший опыт взаимодействия с приложением при возникновении ошибок.
Например, Facebook Messenger охватывает содержимое боковой и информационной панелей, журнала и поля ввода сообщений отдельными предохранителями. Если один из этих компонентов UI упадёт, то остальные сохранят интерактивность.
Также мы призываем пользоваться сервисами обработки ошибок JavaScript (или написать собственный аналогичный сервис), чтобы вы знали и могли устранять необработанные исключения в продакшен-режиме.
Стек вызовов компонентов {#component-stack-traces}
В режиме разработки React 16 выводит на консоль сообщения обо всех ошибках, возникших при рендеринге, даже если они никак не сказались на работе приложения. Помимо сообщения об ошибке и стека JavaScript, React 16 также выводит и стек вызовов компонентов. Теперь вы можете увидеть, где именно в дереве компонентов произошел сбой:

Кроме этого, в стеке вызовов компонентов выводятся имена файлов и номера строк. Такое поведение по умолчанию настроено в проектах, созданных при помощи Create React App:

Если вы не пользуетесь Create React App, вы можете вручную добавить к вашей конфигурации Babel вот этот плагин. Обратите внимание, что он предназначен исключительно для режима разработки и должен быть отключён в продакшене.
Примечание
Имена компонентов, выводимые в их стеке вызовов, определяются свойством
Function.name. Если ваше приложение поддерживает более старые браузеры и устройства, которые могут ещё не предоставлять его нативно (например, IE 11), рассмотрите возможность включения полифиллаFunction.nameв бандл вашего приложения, напримерfunction.name-polyfill. В качестве альтернативы, вы можете явным образом задать пропdisplayNameв каждом из ваших компонентов.
А как насчёт try/catch? {#how-about-trycatch}
try / catch — отличная конструкция, но она работает исключительно в императивном коде:
try { showButton(); } catch (error) { // ... }
В то время, как компоненты React являются декларативными, указывая что должно быть отрендерено:
Предохранители сохраняют декларативную природу React и ведут себя так, как вы уже привыкли ожидать от компонентов React. Например, если ошибка, произошедшая в методе componentDidUpdate, будет вызвана setState где-то в глубине дерева компонентов, она всё равно корректно всплывёт к ближайшему предохранителю.
А что насчёт обработчиков событий? {#how-about-event-handlers}
Предохранители не отлавливают ошибки, произошедшие в обработчиках событий.
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 <button onClick={this.handleClick}>Нажми на меня</button>
}
}
Обратите внимание, что приведённый выше пример демонстрирует стандартное поведение JavaScript и не использует предохранителей.
Изменение названия метода по сравнению с React 15 {#naming-changes-from-react-15}
React 15 включал очень ограниченную поддержку предохранителей с другим названием метода: unstable_handleError. Этот метод больше не работает и вам нужно будет заменить его на componentDidCatch в своем коде, начиная с первого бета-релиза React 16.
Для этого изменения мы предоставили codemod, обеспечивающий автоматический перенос вашего кода.