Границы ошибок react

Error Boundaries

В прошлом ошибки JavaScript внутри компонентов искажали внутреннее состояние React и заставляли его выдавать загадочные ошибки при следующем рендеринге. Эти ошибки всегда были вызваны более ранней ошибкой в ​​коде приложения, но React не предоставлял способ изящной обработки их в компонентах и ​​не мог восстановиться после них.

Представляющие границы ошибок

Ошибка JavaScript в части пользовательского интерфейса не должна ломать все приложение.Чтобы решить эту проблему для пользователей React,в React 16 введено новое понятие «границы ошибки».

Границы ошибок — это компоненты React, которые перехватывают ошибки JavaScript в любом месте своего дочернего дерева компонентов, регистрируют эти ошибки и отображают резервный пользовательский интерфейс вместо дерева компонентов, в котором произошел сбой. Границы ошибок обнаруживают ошибки во время рендеринга, в методах жизненного цикла и в конструкторах всего дерева под ними.

Note

Границы ошибок не обнаруживают ошибок для:

  • Обработчики событий ( подробнее )
  • Асинхронный код (например, обратные вызовы setTimeout или requestAnimationFrame )
  • Рендеринг серверной стороны
  • Ошибки,брошенные в саму границу ошибки (а не ее дочерние элементы)

Компонент класса становится границей ошибки, если он определяет один (или оба) из методов жизненного цикла static getDerivedStateFromError() или componentDidCatch() . Используйте static getDerivedStateFromError() для отображения резервного пользовательского интерфейса после возникновения ошибки. Используйте componentDidCatch() для регистрации информации об ошибках.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Тогда вы можете использовать его как обычный компонент:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

Границы ошибок работают как блок catch {} в JavaScript , но для компонентов. Только компоненты класса могут быть границами ошибки. На практике в большинстве случаев вам потребуется один раз объявить компонент границы ошибки и использовать его во всем приложении.

Обратите внимание, что границы ошибок улавливают ошибки только в компонентах, расположенных ниже них в дереве . Граница ошибки не может поймать ошибку внутри себя. Если границе ошибки не удается отобразить сообщение об ошибке, ошибка будет распространяться на ближайшую границу ошибки над ней. Это тоже похоже на то, как работает блок catch {} в JavaScript.

Live Demo

Посмотрите этот пример объявления и использования границы ошибки .

Где разместить границы ошибок?

Гранулярность границ ошибок зависит от вас.Вы можете обернуть компоненты маршрута верхнего уровня,чтобы вывести на экран сообщение «Что-то пошло не так» для пользователя,подобно тому,как серверные фреймворки часто обрабатывают сбои.Вы также можете обернуть отдельные виджеты в границу ошибки,чтобы защитить их от сбоя остальной части приложения.

Новое Поведение для Недостигнутых Ошибок

Это изменение имеет важное значение. Начиная с React 16, ошибки, которые не были обнаружены ни одной границей ошибок, приводят к отключению всего дерева компонентов React.

Мы обсуждали это решение,но,по нашему опыту,хуже оставить поврежденный пользовательский интерфейс на месте,чем полностью его удалить.Например,в таком продукте,как Messenger,оставление поврежденного пользовательского интерфейса видимым может привести к тому,что кто-то отправит сообщение не тому человеку.Аналогичным образом,для платежного приложения хуже отображать неправильную сумму,чем ничего не отображать.

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

Например,Facebook Messenger обертывает содержимое боковой панели,информационной панели,журнала разговоров и вводимого сообщения в отдельные границы ошибок.Если какой-то компонент в одной из этих областей пользовательского интерфейса выходит из строя,остальные остаются интерактивными.

Мы также рекомендуем вам использовать JS-сервисы отчетности об ошибках (или создать свои собственные),чтобы вы могли узнать о необработанных исключениях,как они происходят в производстве,и исправить их.

Следы стека компонентов

Реакция 16 выводит на печать все ошибки,которые возникли во время рендеринга в консоль при разработке,даже если приложение случайно их проглотило.Кроме сообщения об ошибке и стека JavaScript,он также предоставляет трассировку стека компонентов.Теперь можно увидеть,где именно в дереве компонентов произошел сбой:

Error caught by Error Boundary component

Вы также можете увидеть имена файлов и номера строк в трассировке стека компонентов. Это работает по умолчанию в проектах Create React App :

Error caught by Error Boundary component with line numbers

Если вы не используете Create React App, вы можете вручную добавить этот плагин в конфигурацию Babel. Обратите внимание, что он предназначен только для разработки и должен быть отключен в рабочей среде .

Note

Имена компонентов, отображаемые в трассировке стека, зависят от свойства Function.name . Если вы поддерживаете старые браузеры и устройства, которые могут еще не обеспечивать это изначально (например, IE 11), рассмотрите возможность включения полифилла Function.name в ваше связанное приложение, такого как function.name-polyfill . В качестве альтернативы вы можете явно установить свойство displayName для всех ваших компонентов.

Как насчет «попробуй/поймай»?

try / catch — это здорово, но работает только для императивного кода:

try {
  showButton();
} catch (error) {
  
}

Однако компоненты React декларативны и указывают, что должно отображаться:

<Button />

Границы ошибок сохраняют декларативный характер React и ведут себя так, как и следовало ожидать. Например, даже если в методе componentDidUpdate возникает ошибка, вызванная setState где-то глубоко в дереве, она все равно будет правильно распространяться до ближайшей границы ошибки.

Как насчет организаторов мероприятий?

Границы ошибок не обнаруживают ошибок внутри обработчиков событий.

React не нужны границы ошибок для восстановления после ошибок в обработчиках событий.В отличие от метода рендеринга и методов жизненного цикла,обработчики событий не происходят во время рендеринга.Поэтому,если они сбрасываются,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>Caught an error.</h1>
    }
    return <button onClick={this.handleClick}>Click Me</button>
  }
}

Обратите внимание,что приведенный выше пример демонстрирует обычное поведение JavaScript и не использует границы ошибок.

Название Изменения по сравнению с реакцией 15

React 15 включал очень ограниченную поддержку границ ошибок под другим именем метода: unstable_handleError . Этот метод больше не работает, и вам нужно будет изменить его на componentDidCatch в своем коде, начиная с первой бета-версии 16.

Для этого изменения мы предоставили codemod для автоматического переноса вашего кода.

Является ли эта страница полезной?


React

18.2

  • Создать новое приложение для реакций

    Используйте интегрированную цепочку инструментов для наилучшего взаимодействия с пользователями и разработчиками.

  • DOM Elements

    React реализует независимую от браузера систему DOM для обеспечения производительности и кроссбраузерной совместимости.

  • SyntheticEvent

    Это справочное руководство описывает обертку SyntheticEvent,которая является частью системы React.

  • AJAX и API

    Вы можете использовать любую библиотеку AJAX,как в случае с React.

Выявление ошибок в React с помощью Error Boundaries

От автора: эта статья познакомит вас с концепцией error boundaries в React. Мы рассмотрим, какие задачи они пытаются решить, как их реализовать и какие у них есть недостатки. Наконец, мы рассмотрим небольшой слой абстракции, который делает error boundaries еще лучше!

Даже в самых безупречных приложениях время от времени возникают ошибки выполнения. Сеть может выйти из строя, какая-то внутренняя служба может выйти из строя, или пользователи могут предоставить вам некоторую вводную информацию, которая просто не вычисляется. Или — ну знаете — баги. Но как лучше всего обрабатывать ошибки, чтобы ваше приложение оставалось безотказным, продолжало реагировать и обеспечивать максимально удобное взаимодействие с пользователем?

Что такое error boundaries?

Error boundaries — это способ React обрабатывать ошибки в приложении. Они позволяют вам реагировать и восстанавливаться после ошибок времени выполнения, а также предоставляют резервный пользовательский интерфейс, если это применимо.

Идея, лежащая в основе error boundaries, заключается в том, что вы можете заключить любую часть вашего приложения в специальный компонент — так называемую границу ошибки (error boundary)- и если в этой части приложения возникнет неперехваченная ошибка, она будет содержаться в этом компоненте. Затем вы можете показать ошибку, сообщить об этом в службу отчетов об ошибках и попытаться ее исправить, если это возможно.

Error boundaries были введены в React 16 и были одной из первых функций, появившихся в результате усилий команды React по переписыванию Fiber. Это единственный компонент, который вам все еще нужно написать как компонент класса (так что пока никаких хуков!), Но он определенно должен быть частью любого современного приложения React.

Профессия Frontend-разработчик PRO

Готовим Frontend-разработчиков с нуля

На курсе вы научитесь создавать интерфейсы веб-сервисов с помощью языков программирования и
дополнительных технологий. Сможете разрабатывать планировщики задач, мессенджеры, интернет-магазины…

Узнать подробнее

До 10 проектов в портфолио для старта карьеры

Подходит для новичков без опыта в программировании

Практика на вебинарах с разработчиками из крупных компаний

Обратите внимание, что даже несмотря на то, что вы можете создать несколько error boundaries в своем приложении, как правило, выбирают только одну на корневом уровне. При желании вы можете использовать супер-грануляцию, но мой опыт подсказывает, что часто бывает достаточно корневого уровня.

Моя первая error boundary

Error boundary — это обычный компонент класса, который реализует один (или оба) из следующих методов:

static getDerivedStateFromError(error)

Этот метод возвращает новое состояние на основе обнаруженной ошибки. Обычно вы меняете флаг состояния, который сообщает error boundary, следует ли предоставлять резервный пользовательский интерфейс.

componentDidCatch(error, errorInfo)

Этот метод вызывается всякий раз, когда возникает ошибка. Вы можете зарегистрировать ошибку (и любую дополнительную информацию) в службе отчетов об ошибках, попытаться исправить ошибку и сделать все, что вам нужно.

Чтобы показать, как это реализовано, давайте сделаем несколько шагов. Во-первых, давайте создадим компонент обычного класса.

class ErrorBoundary extends React.Component {

    render() {

    return this.props.children;

    }  

}

Этот компонент почти ничего не делает — он просто отображает своих дочерних элементов. Зарегистрируем ошибку в сервисе ошибок!

class ErrorBoundary extends React.Component {

    componentDidCatch(error, errorInfo) {

        errorService.log({ error, errorInfo });

    }

    render() {

        return this.props.children;

    }  

}

Теперь, когда у пользователей возникает ошибка, мы получаем уведомление через службу отчетов об ошибках. Мы получим саму ошибку, а также полный стек компонентов, в которых произошла ошибка. Это значительно упростит работу по исправлению ошибок в дальнейшем!

Однако мы все еще нарушаем работу приложения! Это плохо. Давайте предоставим резервный пользовательский интерфейс. Для этого нам нужно отслеживать, находимся ли мы в ошибочном состоянии — и именно здесь на помощь приходит статический метод getDerivedStateFromError!

class ErrorBoundary extends React.Component {

    state = { hasError: false };

    static getDerivedStateFromError(error) {

        return { hasError: true };

    }

    componentDidCatch(error, errorInfo) {

        errorService.log({ error, errorInfo });

    }

    render() {

        if (this.state.hasError) {

            return <h1>Oops, we done goofed up</h1>;

        }

        return this.props.children;

    }  

}

И теперь у нас есть базовая, но функциональная Error boundary!

Начнем использовать Error boundary

Теперь приступим к ее использованию. Просто оберните компонент корневого приложения в новый компонент ErrorBoundary!

ReactDOM.render(

    <ErrorBoundary>

        <App />

    </ErrorBoundary>,

    document.getElementById(‘root’)

)

Обратите внимание, что вы можете использовать error boundaries так, чтобы они также отображали базовый макет (верхний колонтитул, нижний колонтитул и т. Д.).

Добавим функцию reset!

Иногда подобные ошибки случаются, когда пользовательский интерфейс переходит в нестабильное состояние. Всякий раз, когда возникает ошибка, все поддерево error boundary размонтируется, что, в свою очередь, сбрасывает любое внутреннее состояние.

Предоставление пользователю кнопки «Хочу повторить попытку», которая будет пытаться перемонтировать поддерево с новым состоянием, иногда может быть хорошей идеей! Давайте сделаем это.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

class ErrorBoundary extends React.Component {

    state = { hasError: false };

    static getDerivedStateFromError(error) {

        return { hasError: true };

    }

    componentDidCatch(error, errorInfo) {

        errorService.log({ error, errorInfo });

    }

    render() {

        if (this.state.hasError) {

            return (

            <div>

                <h1>Oops, we done goofed up</h1>

                <button type=«button» onClick={() => this.setState({ hasError: false })}>

                Try again?

                </button>

            </div>

            );

        }

        return this.props.children;

    }  

}

Конечно, это может быть не очень хорошей идеей для приложения. При реализации подобных функций учитывайте свои потребности и потребности пользователей.

Инструмент воспроизведения сеанса с открытым исходным кодом

Отладка веб-приложения в производственной среде может быть сложной задачей и потребовать много времени. OpenReplay — это альтернатива с открытым исходным кодом для FullStory, LogRocket и Hotjar. Он позволяет отслеживать и воспроизводить все, что делают ваши пользователи, и показывает, как ваше приложение ведет себя при каждой проблеме. Это похоже на то, как если бы инспектор вашего браузера был открыт, когда вы смотрите через плечо пользователя. OpenReplay — единственная доступная альтернатива с открытым исходным кодом.

Ограничения

Error boundaries отлично подходят для того, что они делают — вылавливают ошибки времени выполнения, которых вы не ожидали во время рендеринга. Однако есть несколько типов ошибок, которые не обнаруживаются, и с которыми нужно справляться другим способом. К ним относятся:

ошибки в обработчиках событий (например, при нажатии кнопки)

ошибки в асинхронных обратных вызовах (например, setTimeout)

ошибки, которые происходят в самом компоненте error boundary

ошибки, возникающие при рендеринге на стороне сервера

Эти ограничения могут показаться серьезными, но в большинстве случаев их можно обойти, используя try-catch и hasError.

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>Sign up failed!</p>;

    }

    return <button onClick={handleClick}>Sign up</button>;

}

Это работает достаточно хорошо, даже если вам нужно продублировать несколько строк кода.

Создание лучшей Error boundary

Error boundaries хороши по умолчанию, но было бы неплохо повторно использовать их логику обработки ошибок в обработчиках событий и асинхронных местах. Это достаточно просто реализовать через контекстный API! Давайте реализуем функцию для запуска ошибок вручную.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class ErrorBoundary extends React.Component {

    state = { hasError: false };

    static getDerivedStateFromError(error) {

        return { hasError: true };

    }

    componentDidCatch(error, errorInfo) {

        errorService.log({ error, errorInfo });

    }

    triggerError = ({ error, errorInfo }) => {

        errorService.log({ error, errorInfo });

        this.setState({ hasError: true });

    }

    resetError = () => this.setState({ hasError: false });

    render() {

        if (this.state.hasError) {

            return <h1>Oops, we done goofed up</h1>;

        }

        return this.props.children;

    }  

}

Затем давайте создадим контекст и передадим в него нашу новую функцию:

const ErrorBoundaryContext = React.createContext(() => {});

Затем мы можем создать пользовательский хук для извлечения функции запуска ошибки из любого дочернего компонента:

const useErrorHandling = () => {

    return React.useContext(ErrorBoundaryContext)

}

Затем давайте обернем нашу error boundary в контексте:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

class ErrorBoundary extends React.Component {

    state = { hasError: false };

    static getDerivedStateFromError(error) {

        return { hasError: true };

    }

    componentDidCatch(error, errorInfo) {

        errorService.log({ error, errorInfo });

    }

    triggerError = ({ error, errorInfo }) => {

        errorService.log({ error, errorInfo });

        this.setState({ hasError: true });

    }

    resetError = () => this.setState({ hasError: false });

    render() {

        return (

            <ErrorBoundaryContext.Provider value={this.triggerError}>

            {this.state.hasError

                ? <h1>Oops, we done goofed up</h1>

                : this.props.children

            }

            </ErrorBoundaryContext.Provider>

        );

    }  

}

Теперь мы можем запускать ошибки и из наших обработчиков событий!

function SignUpButton(props) {

    const { triggerError } = useErrorHandling();

    const handleClick = async () => {

        try {

            await api.signUp();

        } catch(error) {

            triggerError(error);

        }

    }

    return <button onClick={handleClick}>Sign up</button>;

}

Теперь нам не нужно думать об отчетах об ошибках или создании резервного пользовательского интерфейса для каждого реализованного нами обработчика кликов — все это находится в компоненте error boundary.

Использование react-error-boundary

Написание собственной логики error boundary, как мы делали выше, — это нормально, и вам подойдет большинство вариантов использования. Однако это решенная проблема. Член команды React Core Брайан Вон (а позже очень талантливый преподаватель React Кент С. Доддс) потратил немного времени на создание [react-error-boundary] (https://www.npmjs.com/package/react-error-boundary) пакета npm, который дает вам почти то же самое, что и выше.

API немного отличается, поэтому вы можете передавать пользовательские резервные компоненты и логику reset вместо написания своей собственной, но он используется очень похожим образом. Вот пример:

ReactDOM.render(

    <ErrorBoundary

        FallbackComponent={MyFallbackComponent}

        onError={(error, errorInfo) => errorService.log({ error, errorInfo })}

    >

        <App />

    </ErrorBoundary>,

    document.getElementById(‘root’)

)

Вы также можете посмотреть, как это реализовано — он использует другой подход к запуску ошибок из хуков, но в остальном все работает примерно так же.

Заключение

Обработка ошибок и неожиданных событий имеет решающее значение для любого качественного приложения. Чрезвычайно важно обеспечить удобство работы пользователей, даже если все идет не так, как планировалось.
Error boundaries — отличный способ заставить ваше приложение упасть изящно и даже содержать ошибки, которые привели к падению, в то время как остальная часть приложения продолжит работать! Напишите свой собственный или воспользуйтесь библиотекой react-error-boundary, которая сделает все за вас. Независимо от того, что вы выберете, пользователи будут вам благодарны!

Автор: Kristofer Selbekk

Источник: blog.openreplay.com

Редакция: Команда webformyself.

Профессия Frontend-разработчик PRO

Готовим Frontend-разработчиков с нуля

На курсе вы научитесь создавать интерфейсы веб-сервисов с помощью языков программирования и
дополнительных технологий. Сможете разрабатывать планировщики задач, мессенджеры, интернет-магазины…

Узнать подробнее

До 10 проектов в портфолио для старта карьеры

Подходит для новичков без опыта в программировании

Практика на вебинарах с разработчиками из крупных компаний

Читайте нас в Telegram, VK, Яндекс.Дзен

Error Boundaries: Error Boundaries basically provide some sort of boundaries or checks on errors, They are React components that are used to handle JavaScript errors in their child component tree.

React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI. It catches errors during rendering, in lifecycle methods, etc.

Reason to Use: Suppose there is an error in JavaScript inside component then it used to corrupt React’s internal state and cause it to emit cryptic errors. Error boundaries help in removing these errors and display a Fallback UI instead(Which means a display of an error that something broke in the code).

Working Principle: Error Boundary works almost similar to catch in JavaScript. Suppose an error is encountered then what happens is as soon as there is a broken JavaScript part in Rendering or Lifecycle Methods, It tries to find the nearest Error Boundaries Tag.

Creating React Application: 

Step 1: Create a React application using the following command:

create-react-app error-boundary

Step 2: After creating the error-boundary directory move to it.

cd error-boundary

Project Structure: It will look like the following.

Example: Now write down the following code in the App.js file. Here, App is our default component where we have written our code. 

Filename: App.js

Javascript

import React from "react";

class ErrorBoundary extends React.Component {

  constructor(props) {

    super(props);

    this.state = { error: null, errorInfo: null };

  }

  componentDidCatch(error, errorInfo) {

    this.setState({

      error: error,

      errorInfo: errorInfo

    })

  }

  render() {

    if (this.state.errorInfo) {

      return (

        <div>

          <h2>An Error Has Occurred</h2>

          <details>

            {this.state.error && this.state.error.toString()}

            <br />

            {this.state.errorInfo.componentStack}

          </details>

        </div>

      );

    }

    return this.props.children;

  }

}

class Counter extends React.Component {

  constructor(props) {

    super(props);

    this.state = { counter: 0 };

    this.handleClick = this.handleClick.bind(this);

  }

  handleClick() {

    this.setState(({ counter }) => ({

      counter: counter + 1

    }));

  }

  render() {

    if (this.state.counter === 3) {

      throw new Error('Crashed!!!!');

    }

    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;

  }

}

function App() {

  return (

    <div style={{ marginLeft: '30px', marginTop: '50px' }}>

      <div style={{ textAlign: "center" }}>

        <h1>

          <strong>To see the working of Error boundaries

           click on the Counters to increase the value

          </strong>

        </h1>

<p>

          Program is made such a way that as soon as the counter

          reaches the value of 3, Error boundaries will throw an 

          error.

        </p>

      </div>

      <hr style={{ width: "500px" }} />

      <ErrorBoundary>

<p>

          These two counters are inside the same error boundary.

          If one crashes, then the effect will be done on both 

          as the error boundary will replace both of them.

        </p>

        <Counter />

        <Counter />

      </ErrorBoundary>

      <hr style={{ width: "500px" }} />

<p>

        These two counters are each inside of their 

        own error boundary. So if one crashes, the 

        other is not affected.

      </p>

      <ErrorBoundary><Counter /></ErrorBoundary>

      <ErrorBoundary><Counter /></ErrorBoundary>

    </div>

  );

}

export default App;

 
Filename: index.js

Javascript

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

import App from './App';

import reportWebVitals from './reportWebVitals';

ReactDOM.render(

  <React.StrictMode>

    <App />

  </React.StrictMode>,

  document.getElementById('root')

);

reportWebVitals();

Step to Run Application: Run the application using the following command from the root directory of the project. 

npm start

Output: Now open your browser and go to http://localhost:3000/, you will see the following output. 

Explanation: The above code is written in such a way that if the counter reaches the value of 3 then Error Boundaries will throw an error.

As shown in the above code that two counters are included in the same Error Boundary Component through which if any one of them causes any sort of error by reaching the value of 3, then instead of rendering any of them a detailed message will be provided on the screen.

On the other end below both counters are included in the individual error Boundaries component through which what happens is only that counter which has caused the error is not rendered, while others are rendered normally.

Error boundaries do not catch errors for the following events:

  • Event Handlers
  • Asynchronous code(Example request Animation Frame etc)
  • Server-Side Rendering
  • Errors are thrown in the error boundary itself (rather than its children)

Try/Catch: One question which might be tickling in your mind is since Error Boundaries works like Catch, Why not just go with try/catch and why should you learn this new Concept. Well, the answer is try/catch is used with imperative code but As we know that React is declarative in nature, and Error Boundaries help in preserving the declarative nature of React.

Uncaught changes: Since it does not catch errors in some particular cases, So what about those errors that left unchecked or Uncaught. As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree. This means after migrating to React 16 and using Error Boundaries, you will be able to provide a better user experience as now users will be able to see the reason before an unexpected crash, instead of just guessing.

Component Stack Trace: React 16 prints all errors that occurred, it provides component Stack Trace. This helps the user in identifying the point where an error has occurred.

Event Listeners: Error Boundaries does not check errors in event handlers, So should this be counted as some sort of Limitation of Error Boundaries, Well the answer is no, the Reason being Event Listeners does not happen during rendering, So if any error is caused due to them React will simply display it on the screen. 

Error Boundaries:

  • It can only be used with Class Components.
  • It does not catch errors for Event Handlers, Asynchronous code(Example request Animation Frame), Server Side Rendering, and Errors are thrown in the error boundary itself (rather than its children).
  • It is available only in react 16 or after.

Reference: https://reactjs.org/docs/error-boundaries.html

Last Updated :
04 Apr, 2023

Like Article

Save Article

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

В этой статье мы пройдёмся по различным способам справиться с ошибками в приложениях на React.

 автор KOTELOV

автор KOTELOV

Классический метод «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, или requestAnimationFrame Callbacks).

  • 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 помогает писать меньше кода.

  • Вы также можете запустить свою собственную границу ошибок и настроить ее так, как хотите.

Понравилась статья? Поделить с друзьями:
  • Гранд эво стиральная машина ошибка е03
  • Гранд чероки ошибка р1762
  • Гранд чероки ошибка р06dd
  • Гранд чероки ошибка p0702
  • Гранд чероки ошибка p06dd