React обработка ошибок сервера

Время на прочтение
4 мин

Количество просмотров 10K

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

Ошибки во время работы или белый экран с ошибками должны быть качественно обработаны. Для этого нам и понадобится React Error Boundary. В React добавили Error Boundary для отлавливания JavaScript ошибок и эффективной обработки их. Как сказано в react документации, Error Boundary — это компоненты React, которые отлавливают ошибки JavaScript в любом месте деревьев их дочерних компонентов, сохраняют их в журнале ошибок и выводят запасной UI вместо рухнувшего дерева компонентов. До дня, когда написана данная статья, react boundaries поддерживаются только как классовые компоненты. Следовательно, когда вы используете React с хуками, то это будет единственный классовый компонент, который вам понадобится.

Но хватит теории, давайте погружаться в код.

Давайте создадим классовый компонент, и используем его как error boundary. Вот код –

class ErrorBoundary extends Component {
    state = {
        error: null,
    };
    static getDerivedStateFromError(error) {
        return { error };
    }
    render() {
        const { error } = this.state;

        if (error) {
            return (
                <div>
                    <p>Seems like an error occured!</p>
                    <p>{error.message}</p>
                </div>
            );
        }
        return this.props.children;
    }
}

export default ErrorBoundary;

В коде выше вы увидите статичную функцию getDerivedStateFromError(error). Данная функция превращает классовый компонент ErrorBoundary в компонент, который действительно обрабатывает ошибки.

Мы отлавливаем ошибки внутри функции getDerivedStateFromError и помещаем их в состояние компонента. Если ошибка произошла, то мы отображаем её текст (пока что), а если нету, то просто возвращаем компонент, который должен отображаться.

Теперь, давайте посмотрим где мы можем использовать этот Error Boundary. Представьте, вы отображаете список пользователей, который получаете из API. Это выглядит примерно так –

const Users = ({ userData, handleMoreDetails }) => {
    return (
        <div>
            <h1>Users List: </h1>

            <ul>
                {userData.map((user) => (
                    <div key={user.id}>
                        <p>Name: {user.name}</p>
                        <p>Company: {user.company}</p>
                        <button onClick={() => handleMoreDetails(user.id)}>
                            More details
                        </button>
                    </div>
                ))}
            </ul>
        </div>
    );
};

Компонент User будет прекрасно работать, пока у нас всё в порядке с получением данных из userData. Но, если по какой-то причине userData будет undefined или null, наше приложение будет сломано! Так что, давайте добавим Error Boundary в данный компонент. После добавления наш код будет выглядеть вот так –

const Users = ({ userData, handleMoreDetails }) => {
    return (
        <div>
            <h1>Users List: </h1>
            <ErrorBoundary>
                <ul>
                    {userData.map((user) => (
                        <div key={user.id}>
                            <p>Name: {user.name}</p>
                            <p>Company: {user.company}</p>
                            <button onClick={() => handleMoreDetails(user.id)}>
                                More details
                            </button>
                        </div>
                    ))}
                </ul>
            </ErrorBoundary>
        </div>
    );
};

Когда ошибка произойдет, наш Error Boundary компонент отловит ошибку, и текст данной ошибки будет отображен на экране. Это спасет приложение от поломки, и пользователь поймет, что пошло не так.

Важный пункт, это выбрать, где мы используем Error Boundary, т.к. ошибка будет отображаться вместо компонента. Следовательно, нам нужно всегда быть уверенными, где будет отображаться текст данной ошибки. В нашем примере, мы хотим показывать верхнюю часть страницы, и все остальные данные. Нам всего лишь нужно заменить компонент, где ошибка произошла, и, в данной ситуации, это просто элемент ul. И мы выбираем использовать только ul элемент внутри Error Boundary, а не весь компонент целиком.

На текущий момент мы уже поняли, что такое Error Boundary и как использовать его. А вот наше место отображения ошибки выглядит не особо хорошо, и может быть улучшено. Способы, как мы отображаем ошибки и как “ломаются” наши компоненты будут отличаться от ситуации к ситуации. Так что нам надо сделать наш Error Boundary компонент более адаптивным к этим ситуациям.

Для этого мы создадим проп ErrorComponent внутри Error Boundary, и будем возвращать элемент, который прокиним внутрь данного пропа, чтобы он отображался во время ошибки. Ниже приведены финальный версии Error Boundary и User компонентов –

// User Component 

const Users = ({ userData, handleMoreDetails }) => {
    const ErrorMsg = (error) => {
        return (
            <div>
                {/* Вы можете использовать свои стили и код для обработки ошибок */}
                <p>Something went wrong!</p>
                <p>{error.message}</p>
            </div>
        );
    };

    return (
        <div>
            <h1>Users List: </h1>
            <ErrorBoundary ErrorComponent={ErrorMsg}>
                <ul>
                    {userData.map((user) => (
                        <div key={user.id}>
                            <p>Name: {user.name}</p>
                            <p>Company: {user.company}</p>
                            <button onClick={() => handleMoreDetails(user.id)}>
                                More details
                            </button>
                        </div>
                    ))}
                </ul>
            </ErrorBoundary>
        </div>
    );
};
// ErrorBoundary Component
class ErrorBoundary extends Component {
    state = {
        error: null,
    };
    static getDerivedStateFromError(error) {
        return { error };
    }
    render() {
        const { error } = this.state;

        if (error) {
            return <this.props.ErrorComponent error={error} />;
        }
        return this.props.children;
    }
}

Вы можете также передавать проп key внутрь компонента Error Boundary, если вам нужно отображать несколько ошибок внутри одного компонента. Это позволит более гибко настроить данные ошибки для каждого элемента.

Error Boundary – это одна из приятных фишек React, которая, в свою очередь, я вижу что сравнительно редко используется. Но использование этого в вашем коде, будьте уверены, спасет вас от неловких моментов при внезапных ошибках. И кто не хочет лучше обрабатывать свои ошибки. 😉

В ситуации, когда вы не хотите писать свой собственный Error Boundary компонент, для этого можно использовать react-error-boundary.

А на этом пост заканчивается. Поделитесь своими мыслями в комментариях. И не забывайте, всегда продолжайте учиться!

Обрабатываем ошибки в React: полное руководство


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

Рассмотрим обработку ошибок в React: что делать при их появлении, как их выявить и устранить.

Почему нужно находить ошибки в React

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

Выявление ошибок в JavaScript

В обычном JavaScript для выявления ошибок есть довольно простые инструменты. Например, оператор try/catch: попытаться (try) что-то выполнить, а если не получится, то поймать (catch) ошибку и сделать что-нибудь, чтобы минимизировать ее последствия.

try {
// некорректная операция может вызвать ошибку
doSomething();
} catch (e) {
// если ошибка произошла, ловим ее и делаем что-нибудь без остановки приложения,
// например отправляем ее в службу регистрации
}

Для функции async синтаксис будет такой же:

try {
await fetch('/bla-bla');
} catch (e) {
// Выборка не удалась! С этим нужно что-то делать!
}

Для традиционных промисов есть метод catch. Предыдущий пример fetch с API на основе промиса можно переписать так:

fetch('/bla-bla').then((result) => {
// Если промис выполнен успешно, результат будет здесь,
// с ним можно сделать что-нибудь полезное
}).catch((e) => {
// О нет, выборка не удалась! Нужно что-то с этим сделать!
})

Это та же концепция, только немного другая реализация, поэтому и далее для всех ошибок используем синтаксис try/catch.

Простой try/catch в React: как правильно его выполнить

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

Наиболее очевидным и интуитивно понятным решением будет рендеринг на экране чего-либо до исправления ситуации. К счастью, оператор catch предоставляет для этого ряд возможностей, включая установку состояния. Например:

const SomeComponent = () => {
const [hasError, setHasError] = useState(false);

useEffect(() => {
try {
// делаем что-либо, например выборку данных
} catch(e) {
// выборка не прошла, данных для рендеринга нет!
setHasError(true);
}
})

// что-то произошло во время выборки, отобразим красивый экран с ошибкой
if (hasError) return <SomeErrorScreen />

// данные есть - отрендерим их
return <SomeComponentContent {...datasomething} /

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

Этот способ подходит для простых, предсказуемых и ограниченных вариантов использования, таких как обнаружение неудачного запроса fetch.

Но если вы захотите отловить все возможные варианты ошибок в компоненте, то столкнетесь с определенными проблемами и серьезными ограничениями.

Ограничение 1: проблемы с хуком useEffect

Если просто обернуть useEffect с помощью try/catch, это не сработает:

try {
useEffect(() => {
throw new Error('Hulk smash!');
}, [])
} catch(e) {
// useEffect выбрасывается, но не вызывается
}

Дело в том, что useEffect вызывается асинхронно после рендеринга, поэтому для try/catch все проходит успешно. Подобное происходит и с любым Promise: если не ожидать результата, JavaScript просто продолжит свое дело, вернется к нему, когда промис будет выполнен, и выполнит только то, что находится внутри useEffect (и затем промиса). Выполненный блок try/catch исчезнет к тому времени.

Чтобы отлавливать ошибки внутри useEffect, нужно также поместить try/catch внутрь:

useEffect(() => {
try {
throw new Error('Hulk smash!');
} catch(e) {
// эта ошибка будет перехвачена
}
}, [])

Поэкспериментируйте с этим примером.

Это относится к любому хуку, использующему useEffect, и ко всем асинхронным действиям. В результате вместо одного try/catch, обертывающего все, придется разбить его на несколько блоков: по одному на каждый хук.

Ограничение 2: дочерние компоненты

try/catch не сможет поймать ошибку внутри дочерних компонентов. Например:

const Component = () => {
let child;

try {
child = <Child />
} catch(e) {
// бесполезен для отлова ошибок внутри дочернего компонента, не будет запускаться
}
return child;
}

Или даже так:

const Component = () => {
try {
return <Child />
} catch(e) {
// по-прежнему бесполезен для обнаружения ошибок внутри дочернего компонента, не будет запускаться
}
}

Убедитесь на этом примере.

После Child /> нет реального рендеринга компонента. Мы создаем Element компонента, который является его определением. Это просто объект, который содержит необходимую информацию, такую как тип компонента и реквизиты, которые позже будут использоваться самим React, что фактически и вызовет рендеринг этого компонента. И произойдет это после успешного выполнения блока try/catch. Та же ситуация, что с промисами и хуком useEffect.

Ограничение 3: нельзя установить состояние во время рендеринга

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

Вот пример простого кода, который вызовет бесконечный цикл повторных рендеров, если произойдет ошибка:

const Component = () => {
const [hasError, setHasError] = useState(false);

try {
doSomethingComplicated();
} catch(e) {
// недопустимый вариант! В случае ошибки вызовет бесконечный цикл
// см. реальный пример в codesandbox ниже
setHasError(true);
}
}

Убедитесь сами в codesandbox.

Конечно, можно просто отобразить экран ошибки вместо установки состояния:

const Component = () => {
try {
doSomethingComplicated();
} catch(e) {
// допустимый вариант
return <SomeErrorScreen />
}
}

Но это немного громоздко и заставит по-разному обрабатывать ошибки в одном и том же компоненте: состояние для useEffect и обратных вызовов, а также прямой возврат для всего остального.

// это рабочий, но громоздкий вариант, не заслуживающий внимания
const SomeComponent = () => {
const [hasError, setHasError] = useState(false);

useEffect(() => {
try {
// делаем что-либо, например выборку данных
} catch(e) {
// невозможен простой return в случае ошибок в useEffect и callbacks,
// поэтому приходится использовать состояние
setHasError(true);
}
})

try {
// делаем что-либо во время рендеринга
} catch(e) {
// но здесь мы не можем использовать состояние, поэтому в случае ошибки нужно возвращать напрямую
return <SomeErrorScreen />;
}

// и все же нужен return в случае ошибки состояния
if (hasError) return <SomeErrorScreen />
return <SomeComponentContent {...datasomething} />
}

В итоге, если в React полагаться исключительно на try/catch, то мы либо пропустим большую часть ошибок, либо превратим каждый компонент в непонятную смесь кода, которая, вероятно, сама по себе вызовет ошибки.

К счастью, есть и другой способ.

Компонент React ErrorBoundary

Обойти отмеченные выше ограничения позволяет React Error Boundaries. Это специальный API, который превращает обычный компонент в оператор try/catch в некотором роде только для декларативного кода React. Типичное использование будет примерно таким:

const Component = () => {
return (
<ErrorBoundary>
<SomeChildComponent />
<AnotherChildComponent />
</ErrorBoundary>
)
}

Теперь, если в этих компонентах или их дочерних элементах что-то пойдет не так во время рендеринга, ошибка будет обнаружена и обработана.

Но React не предоставляет компонент как таковой, а просто дает инструмент для его реализации. Простейшая реализация будет примерно такой:

class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
// инициализировать состояние ошибки
this.state = { hasError: false };
}

// если произошла ошибка, установите состояние в true
static getDerivedStateFromError(error) {
return { hasError: true };
}

render() {
// если произошла ошибка, вернуть резервный компонент
if (this.state.hasError) {
return <>Oh no! Epic fail!</>
}

return this.props.children;
}
}

Мы создаем компонент класса regular и реализуем метод getDerivedStateFromError, который возвращает компонент в надлежащие границы ошибок.

Кроме того, при работе с ошибками важно отправить информацию о них в сервис обработки. Для этого в Error Boundary есть метод componentDidCatch:

class ErrorBoundary extends React.Component {
// все остальное остается прежним

componentDidCatch(error, errorInfo) {
// отправить информацию об ошибке
log(error, errorInfo);
}
}

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

render() {
// если произошла ошибка, вернуть резервный компонент
if (this.state.hasError) {
return this.props.fallback;
}

return this.props.children;
}

Используем таким образом:

const Component = () => {
return (
<ErrorBoundary fallback={<>Oh no! Do something!</>}>
<SomeChildComponent />
<AnotherChildComponent />
</ErrorBoundary>
)
}

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

Полный пример в codesandbox.

Однако есть одно предостережение: улавливаются не все ошибки.

Компонент ErrorBoundary: ограничения

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

const Component = () => {
useEffect(() => {
// будет пойман компонентом ErrorBoundary
throw new Error('Destroy everything!');
}, [])

const onClick = () => {
// эта ошибка просто исчезнет в void
throw new Error('Hulk smash!');
}

useEffect(() => {
// если это не сработает, ошибка тоже исчезнет
fetch('/bla')
}, [])
return <button onClick={onClick}>click me</button>
}

const ComponentWithBoundary = () => {
return (
<ErrorBoundary>
<Component />
</ErrorBoundary>
)
}

Общей рекомендацией для ошибок такого рода является использование обычных try/catch. По крайней мере здесь мы можем более или менее безопасно использовать состояние: обратные вызовы обработчиков событий  —  это как раз те места, где обычно устанавливают состояние. Итак, технически можно просто объединить два подхода, например:

const Component = () => {
const [hasError, setHasError] = useState(false);

// большинство ошибок в этом и в дочерних компонентах будут перехвачены ErrorBoundary

const onClick = () => {
try {
// эта ошибка будет поймана catch
throw new Error('Hulk smash!');
} catch(e) {
setHasError(true);
}
}

if (hasError) return 'something went wrong';

return <button onClick={onClick}>click me</button>
}

const ComponentWithBoundary = () => {
return (
<ErrorBoundary fallback={"Oh no! Something went wrong"}>
<Component />
</ErrorBoundary>
)
}

Мы вернулись к исходной ситуации: каждый компонент должен поддерживать свое состояние «ошибка» и, что более важно, принимать решение о том, что с ним делать.

Конечно, вместо того чтобы обрабатывать эти ошибки на уровне компонентов, можно просто передавать их до родителя, у которого есть ErrorBoundary, через пропсы или Context. Таким образом, по крайней мере можно иметь «резервный» компонент только в одном месте:

const Component = ({ onError }) => {
const onClick = () => {
try {
throw new Error('Hulk smash!');
} catch(e) {
// просто вызовите пропс вместо сохранения здесь состояния
onError();
}
}

return <button onClick={onClick}>click me</button>
}

const ComponentWithBoundary = () => {
const [hasError, setHasError] = useState();
const fallback = "Oh no! Something went wrong";

if (hasError) return fallback;

return (
<ErrorBoundary fallback={fallback}>
<Component onError={() => setHasError(true)} />
</ErrorBoundary>
)
}

Но здесь много дополнительного кода! Так пришлось бы делать для каждого дочернего компонента в дереве рендеринга. Не говоря уже о том, что сейчас мы обрабатываем два состояния ошибки: в родительском компоненте и в самом ErrorBoundary. А у ErrorBoundary уже есть все механизмы для распространения ошибок вверх по дереву  —  здесь мы делаем двойную работу.

Разве нельзя просто перехватывать эти ошибки из асинхронного кода и обработчиков событий с помощью ErrorBoundary?

Поиск асинхронных ошибок с помощью ErrorBoundary

Хитрость заключается в том, чтобы сначала поймать ошибки с помощью try/catch, затем внутри оператора catch запустить обычную повторную визуализацию React, а затем повторно отбросить эти ошибки обратно в жизненный цикл повторной визуализации. Таким образом, ErrorBoundary может перехватывать их, как и любую другую ошибку. И поскольку обновление состояния  —  это способ запуска повторного рендеринга, а функция установки состояния может фактически принимать функцию обновления в качестве аргумента, решение  —  чистая магия.

const Component = () => {
// создать случайное состояние, которое будем использовать для выдачи ошибок
const [state, setState] = useState();

const onClick = () => {
try {
// возникла какая-то проблема
} catch (e) {
// обновление состояния триггера с функцией обновления в качестве аргумента
setState(() => {
// повторно выдать эту ошибку в функции обновления
// будет запущено во время обновления состояния
throw e;
})
}
}
}

Полный пример в этом codesandbox.

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

const useThrowAsyncError = () => {
const [state, setState] = useState();

return (error) => {
setState(() => throw error)
}
}

Используем так:

const Component = () => {
const throwAsyncError = useThrowAsyncError();

useEffect(() => {
fetch('/bla').then().catch((e) => {
// выдать асинхронную ошибку здесь
throwAsyncError(e)
})
})
}

Или можно создать оболочку для обратных вызовов следующим образом:

const useCallbackWithErrorHandling = (callback) => {
const [state, setState] = useState();

return (...args) => {
try {
callback(...args);
} catch(e) {
setState(() => throw e);
}
}
}

Используем так:

const Component = () => {
const onClick = () => {
// выполнить что-либо опасное здесь
}

const onClickWithErrorHandler = useCallbackWithErrorHandling(onClick);

return <button onClick={onClickWithErrorHandler}>click me!</button>
}

Или что-нибудь еще, что душе угодно и требуется приложению. Ошибки теперь не спрячутся.

Полный пример в этом codesandbox.

Можно ли использовать react-error-boundary?

Для тех, кто не любит изобретать велосипед или просто предпочитает библиотеки для уже решенных задач, есть хороший вариант, который реализует гибкий компонент ErrorBoundary и имеет несколько полезных утилит, подобных описанным выше. Это  —  react-error-boundary.

Использовать его или нет  —  вопрос личных предпочтений, стиля программирования и уникальных особенностей компонентов.

Теперь, если в приложении возникнет проблема, вы сможете легко с ней справиться.

И запомните:

  • Блоки try/catch не будут перехватывать ошибки внутри хуков, таких как useEffect, и внутри любых дочерних компонентов.
  • ErrorBoundary их перехватывать может, но не работает с ошибками в асинхронном коде и в обработчиках событий.
  • Тем не менее вы можете заставить ErrorBoundary ловить их. Просто сначала их нужно поймать с помощью try/catch, а затем забросить обратно в жизненный цикл React.

Читайте также:

  • Управление состоянием в React: обзор
  • 9 советов по работе с консолью JavaScript, которые помогут оптимизировать отладку
  • Preact вместо ручной оптимизации React-приложения

Читайте нас в Telegram, VK и Дзен


Перевод статьи Nadia Makarevich: How to handle errors in React: full guide

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:

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:

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:

If you try to render this component somewhere, you’ll get an error like this one:

Crashable component renders error in the console

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’:

React error

Let’s see a typical error boundary component:

We can use ErrorBoundary like so:

Now, when we open our app, we will get a working app with the following:

Error boundary shows the error

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. setTimeout or requestAnimationFrame callbacks)
  • 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:

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:

Clicking a button catches an error and displays error text

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:

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:

Clicking a button causes a delayed error that gets caught

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:

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:

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:

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.

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...catch statement (e.g., async calls like setTimeout, event handlers, server-side rendering, and errors thrown in the error boundary itself).
  • A library like react-error-boundary can 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.

Looking into how to catch and handle errors in React.

Usages, patterns and caveats of try/catch and ErrorBoundary, what’s possible, what’s not, and how to catch all errors, including async and errors from event handlers, with ErrorBoundary.

This article is part of the «Advanced React» book. If you like it, you might like the book as well 😉

Table of Contents

  1. Why we should catch errors in React
  2. Remembering how to catch errors in javascript
  3. Simple try/catch in React: how to and caveats
  4. React ErrorBoundary component
  5. ErrorBoundary component: limitations
  6. Catching async errors with ErrorBoundary
  7. Can I just use react-error-boundary instead?

We all want our apps to be stable, to work perfectly, and cater to every edge case imaginable, isn’t it? But the sad reality is we are all humans (at least that is my assumption), we all make mistakes, and there is no such thing as a bug-free code. No matter how careful we are or how many automated tests we write, there always will be a situation when something goes terribly wrong. The important thing, when it comes to user experience, is to predict that terrible thing, localize it as much as possible, and deal with it in a graceful way until it can be actually fixed.

So today, let’s take a look at error handling in React: what we can do if an error happens, what are the caveats of different approaches to error catching, and how to mitigate them.

Why we should catch errors in React

But first thing first: why it’s vitally important to have some error-catching solution in React?

The answer is simple: starting from version 16, an error thrown during React lifecycle will cause the entire app to unmount itself if not stopped. Before that, components would be preserved on the screen, even if malformed and misbehaved. Now, an unfortunate uncaught error in some insignificant part of the UI, or even some external library that you have no control over, can destroy the entire page and render an empty screen for everyone.

Never before had frontend developers such destructive power 😅

Remembering how to catch errors in javascript

When it comes to catching those nasty surprises in regular javascript, the tools are pretty straightforward.

We have our good old try/catch statement, which is more or less self-explanatory: try to do stuff, and if they fail — catch the mistake and do something to mitigate it:

try {

doSomething();

} catch (e) {

}

This also will work with async function with the same syntax:

try {

await fetch('/bla-bla');

} catch (e) {

}

Or, if we’re going with the old-school promises, we have a catch method specifically for them. So if we re-write the previous fetch example with promised-based API, it will look like this:

fetch('/bla-bla').then((result) => {

}).catch((e) => {

})

It’s the same concept, just a bit different implementation, so for the rest of the article I’m just going to use try/catch syntax for all errors.

Simple try/catch in React: how to and caveats

When an error is caught, we need to do something with it, right? So, what exactly can we do, other than logging it somewhere? Or, to be more precise: what can we do for our users? Just leaving them with an empty screen or broken interface is not exactly user-friendly.

The most obvious and intuitive answer would be to render something while we wait for the fix. Luckily, we can do whatever we want in that catch statement, including setting the state. So we can do something like this:

const SomeComponent = () => {

const [hasError, setHasError] = useState(false);

useEffect(() => {

try {

} catch(e) {

setHasError(true);

}

})

if (hasError) return <SomeErrorScreen />

return <SomeComponentContent {...datasomething} />

}

We’re trying to send a fetch request, if it fails — setting the error state, and if the error state is true, then we render an error screen with some additional info for users, like a support contact number.

This approach is pretty straightforward and works great for simple, predictable, and narrow use cases like catching a failed fetch request.

But if you want to catch all errors that can happen in a component, you’ll face some challenges and serious limitations.

Limitation 1: you will have trouble with useEffect hook.

If we wrap useEffect with try/catch, it just won’t work:

try {

useEffect(() => {

throw new Error('Hulk smash!');

}, [])

} catch(e) {

}

It’s happening because useEffect is called asynchronously after render, so from try/catch perspective everything went successfully. It’s the same story as with any Promise: if we don’t wait for the result, then javascript will just continue with its business, return to it when the promise is done, and only execute what is inside useEffect (or then of a Promise). try/catch block will be executed and long gone by then.

In order for errors inside useEffect to be caught, try/catch should be placed inside as well:

useEffect(() => {

try {

throw new Error('Hulk smash!');

} catch(e) {

}

}, [])

Play around with this example to see it:

This applies to any hook that uses useEffect or to anything asynchronous really. As a result, instead of just one try/catch that wraps everything, you’d have to split it into multiple blocks: one for each hook.

Limitation 2: children components. try/catch won’t be able to catch anything that is happening inside children components. You can’t just do this:

const Component = () => {

let child;

try {

child = <Child />

} catch(e) {

}

return child;

}

or even this:

const Component = () => {

try {

return <Child />

} catch(e) {

}

}

Play around with this example to see it:

This is happening because when we write <Child /> we’re not actually rendering this component. What we’re doing is creating a component Element, which is nothing more than a component’s definition. It’s just an object that contains necessary information like component type and props, that will be used later by React itself, which will actually trigger the render of this component. And it will happen after try/catch block is executed successfully, exactly the same story as with promises and useEffect hook.

If you’re curious to learn in more detail how elements and components work, here is the article for you: The mystery of React Element, children, parents and re-renders

Limitation 3: setting state during render is a no-no

If you’re trying to catch errors outside of useEffect and various callbacks (i.e. during component’s render), then dealing with them properly is not that trivial anymore: state updates during render are not allowed.

Simple code like this, for example, will just cause an infinite loop of re-renders, if an error happens:

const Component = () => {

const [hasError, setHasError] = useState(false);

try {

doSomethingComplicated();

} catch(e) {

setHasError(true);

}

}

Check it out in codesandbox

We could, of course, just return the error screen here instead of setting state:

const Component = () => {

try {

doSomethingComplicated();

} catch(e) {

return <SomeErrorScreen />

}

}

But that, as you can imagine, is a bit cumbersome, and will force us to handle errors in the same component differently: state for useEffect and callbacks, and direct return for everything else.

const SomeComponent = () => {

const [hasError, setHasError] = useState(false);

useEffect(() => {

try {

} catch(e) {

setHasError(true);

}

})

try {

} catch(e) {

return <SomeErrorScreen />;

}

if (hasError) return <SomeErrorScreen />

return <SomeComponentContent {...datasomething} />

}

To summarise this section: if we rely solely on try/catch in React, we will either miss most of the errors, or will turn every component into an incomprehensible mess of code that will probably cause errors by itself.

Luckily, there is another way.

React ErrorBoundary component

To mitigate the limitations from above, React gives us what is known as “Error Boundaries”: a special API that turns a regular component into a try/catch statement in a way, only for React declarative code. Typical usage that you can see in every example over there, including React docs, will be something like this:

const Component = () => {

return (

<ErrorBoundary>

<SomeChildComponent />

<AnotherChildComponent />

</ErrorBoundary>

)

}

Now, if something goes wrong in any of those components or their children during render, the error will be caught and dealt with.

But React doesn’t give us the component per se, it just gives us a tool to implement it. The simplest implementation would be something like this:

class ErrorBoundary extends React.Component {

constructor(props) {

super(props);

this.state = { hasError: false };

}

static getDerivedStateFromError(error) {

return { hasError: true };

}

render() {

if (this.state.hasError) {

return <>Oh no! Epic fail!</>

}

return this.props.children;

}

}

We create a regular class component (going old-school here, no hooks for error boundaries available) and implement getDerivedStateFromError method — that turns the component into a proper error boundary.

Another important thing to do when dealing with errors is to send the error info somewhere where it can wake up everyone who’s on-call. For this, error boundaries give us componentDidCatch method:

class ErrorBoundary extends React.Component {

componentDidCatch(error, errorInfo) {

log(error, errorInfo);

}

}

After the error boundary is set up, we can do whatever we want with it, same as any other component. We can, for example, make it more re-usable and pass the fallback as a prop:

render() {

if (this.state.hasError) {

return this.props.fallback;

}

return this.props.children;

}

And use it like this:

const Component = () => {

return (

<ErrorBoundary fallback={<>Oh no! Do something!</>}>

<SomeChildComponent />

<AnotherChildComponent />

</ErrorBoundary>

)

}

Or anything else that we might need, like resetting state on a button click, differentiating between types of errors, or pushing that error to a context somewhere.

See full example here:

There is one caveat in this error-free world though: it doesn’t catch everything.

ErrorBoundary component: limitations

Error boundary catches only errors that happen during React lifecycle. Things that happen outside of it, like resolved promises, async code with setTimeout, various callbacks and event handlers, will just disappear if not dealt with explicitly.

const Component = () => {

useEffect(() => {

throw new Error('Destroy everything!');

}, [])

const onClick = () => {

throw new Error('Hulk smash!');

}

useEffect(() => {

fetch('/bla')

}, [])

return <button onClick={onClick}>click me</button>

}

const ComponentWithBoundary = () => {

return (

<ErrorBoundary>

<Component />

</ErrorBoundary>

)

}

The common recommendation here is to use regular try/catch for that kind of errors. And at least here we can use state safely (more or less): callbacks of event handlers are exactly the places where we usually set state anyway. So technically, we can just combine two approaches and do something like this:

const Component = () => {

const [hasError, setHasError] = useState(false);

const onClick = () => {

try {

throw new Error('Hulk smash!');

} catch(e) {

setHasError(true);

}

}

if (hasError) return 'something went wrong';

return <button onClick={onClick}>click me</button>

}

const ComponentWithBoundary = () => {

return (

<ErrorBoundary fallback={"Oh no! Something went wrong"}>

<Component />

</ErrorBoundary>

)

}

But. We’re back to square one: every component needs to maintain its “error” state and more importantly — make a decision on what to do with it.

We can, of course, instead of dealing with those errors on a component level just propagate them up to the parent that has ErrorBoundary via props or Context. That way at least we can have a “fallback” component in just one place:

const Component = ({ onError }) => {

const onClick = () => {

try {

throw new Error('Hulk smash!');

} catch(e) {

onError();

}

}

return <button onClick={onClick}>click me</button>

}

const ComponentWithBoundary = () => {

const [hasError, setHasError] = useState();

const fallback = "Oh no! Something went wrong";

if (hasError) return fallback;

return (

<ErrorBoundary fallback={fallback}>

<Component onError={() => setHasError(true)} />

</ErrorBoundary>

)

}

But it’s so much additional code! We’d have to do it for every child component in the render tree. Not to mention that we’re basically maintaining two error states now: in the parent component, and in ErrorBoundary itself. And ErrorBoundary already has all the mechanisms in place to propagate the errors up the tree, we’re doing double work here.

Can’t we just catch those errors from async code and event handlers with ErrorBoundary instead?

Catching async errors with ErrorBoundary

Interestingly enough — we can catch them all with ErrorBoundary! Everyone’s favorite Dan Abramov shares with us a cool hack to achieve exactly that: Throwing Error from hook not caught in error boundary · Issue #14981 · facebook/react.

The trick here is to catch those errors first with try/catch, then inside catch statement trigger normal React re-render, and then re-throw those errors back into the re-render lifecycle. That way ErrorBoundary can catch them as any other error. And since state update is the way to trigger re-render, and state set function can actually accept a updater function as an argument, the solution is pure magic:

const Component = () => {

const [state, setState] = useState();

const onClick = () => {

try {

} catch (e) {

setState(() => {

throw e;

})

}

}

}

Full example here:

The final step here would be to abstract that hack away, so we don’t have to create random states in every component. We can go creative here, and make a hook that gives us an async errors thrower:

const useThrowAsyncError = () => {

const [state, setState] = useState();

return (error) => {

setState(() => throw error)

}

}

And use it like this:

const Component = () => {

const throwAsyncError = useThrowAsyncError();

useEffect(() => {

fetch('/bla').then().catch((e) => {

throwAsyncError(e)

})

})

}

Or, we can create a wrapper for callbacks like this:

const useCallbackWithErrorHandling = (callback) => {

const [state, setState] = useState();

return (...args) => {

try {

callback(...args);

} catch(e) {

setState(() => throw e);

}

}

}

And use it like this:

const Component = () => {

const onClick = () => {

}

const onClickWithErrorHandler = useCallbackWithErrorHandling(onClick);

return <button onClick={onClickWithErrorHandler}>click me!</button>

}

Or anything else that your heart desires and the app requires. No limits! And no errors will get away anymore.

Full example here:

Can I just use react-error-boundary instead?

For those of you, who hate re-inventing the wheel or just prefer libraries for already solved problems, there is a nice one that implements a flexible ErrorBoundary component and has a few useful utils similar to those described above: GitHub — bvaughn/react-error-boundary

Whether to use it or not is just a matter of personal preferences, coding style, and unique situations within your components.


That is all for today, hope from now on if something bad happens in your app, you’ll be able to deal with the situation with ease and elegance.

And remember:

  • try/catch blocks won’t catch errors inside hooks like useEffect and inside any children components
  • ErrorBoundary can catch them, but it won’t catch errors in async code and event handlers
  • Nevertheless, you can make ErrorBoundary catch those, you just need to catch them with try/catch first and then re-throw them back into the React lifecycle

Live long and error-free! ✌🏼

Nadia Makarevich

Nadia Makarevich

Frontend Architect, coder

Worked on architecture of Jira Frontend monorepo (300 devs, 2mil lines of code), Atlassian Design systems. Currently Founding Engineer in Pyn

Follow on Twitter, LinkedIn, Youtube

Get the latest content by email

More of some useful read

На чтение 11 мин. Просмотров 104 Опубликовано

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

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

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

Содержание

  1. Классический метод ‘Try and Catch’ в React
  2. Границы ошибок React
  3. Ловля ошибок в обработчиках событий
  4. Ловля ошибок в вызовах setTimeout
  5. Пакет JavaScript react-error-boundary
  6. Использование собственных границ React
  7. Подводим итоги: Начните работу с обработкой ошибок 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 Error Boundaries. Давайте разберемся, что это такое.

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

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

const CrashableComponent = (props) => {
  return <span>{props.iDontExist.prop}</span>;
};

export default CrashableComponent;

Вход в полноэкранный режим Выход из полноэкранного режима

Если вы попытаетесь вывести этот компонент где-нибудь, то получите ошибку, подобную этой:

Мало того, вся страница будет пустой, и пользователь не сможет ничего сделать или увидеть. Но что же произошло? Мы пытались получить доступ к свойству iDontExist.prop, которое не существует (мы не передаем его компоненту). Это банальный пример, но он показывает, что мы не можем поймать эти ошибки с помощью оператора try...catch.

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

Граница ошибки – это компонент класса, который определяет один (или оба) из методов жизненного цикла static getDerivedStateFromError() или componentDidCatch().

Вот пример того, как информация об ошибке 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)
  • Рендеринг на стороне сервера
  • Ошибки, возникающие в самой границе ошибки (а не в ее дочерних элементах).

Для этих ошибок все еще нужно использовать оператор 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;

Войти в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что внутри handleClick у нас есть блок try и catch, который гарантирует, что наша ошибка будет поймана. Если вы отобразите компонент и попытаетесь щелкнуть его, произойдет следующее:

То же самое мы должны сделать и в других случаях, например, при вызове setTimeout.

Ловля ошибок в вызовах setTimeout

Представьте, что у нас есть похожий компонент кнопки, но этот компонент вызывает 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;

Вход в полноэкранный режим Выход из полноэкранного режима

Через 1 000 миллисекунд обратный вызов setTimeout выдаст ошибку. К счастью, мы обернули логику обратного вызова в try...catch и setError в компоненте. Таким образом, в консоли браузера не будет показана трассировка стека. Кроме того, мы сообщаем пользователю об ошибке. Вот как это выглядит в приложении:

Это все хорошо, так как мы получили страницы нашего приложения, несмотря на ошибки, выскакивающие повсюду в фоновом режиме. Но есть ли более простой способ обработки ошибок без написания собственных границ ошибок? Конечно, есть, и, конечно, он представлен в виде пакета JavaScript. Позвольте представить вам react-error-boundary.

Пакет JavaScript react-error-boundary

Вы можете поместить эту библиотеку в свой 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, которые вы можете использовать в своем приложении. Расширяйте и играйте с ними сколько угодно. Вы можете заставить их получать пользовательские компоненты отката, чтобы настроить способ восстановления после каждой ошибки. Вы также можете сделать так, чтобы они получали реквизит onError и позже вызывали его внутри componentDidCatch. Возможности бесконечны.

Но одно можно сказать наверняка – эта зависимость вам все-таки не понадобилась. Я уверен, что написание собственной границы ошибки принесет чувство достижения, и вы сможете лучше понять ее. Кроме того, кто знает, какие идеи могут прийти вам в голову, когда вы попытаетесь настроить ее.

Подводим итоги: Начните работу с обработкой ошибок React

Спасибо, что прочитали эту статью об обработке ошибок в React. Надеюсь, вам было так же интересно читать и пробовать, как и мне. Весь код с примерами вы можете найти в созданном мной репозитории GitHub.

Кратко о том, что мы рассмотрели:

  • Границы ошибок React Error отлично подходят для отлова ошибок в декларативном коде (например, внутри дерева дочерних компонентов).
  • Для других случаев необходимо использовать оператор try...catch (например, асинхронные вызовы типа setTimeout, обработчики событий, рендеринг на стороне сервера и ошибки, возникающие в самой границе ошибок).
  • Такая библиотека, как react-error-boundary, поможет вам написать меньше кода.
  • Вы также можете запустить свою собственную границу ошибок и настроить ее так, как вам захочется.

Вот и все, друзья. Спасибо за внимание, и до встречи в следующем выпуске!

P.S. Если вам понравился этот пост, подпишитесь на наш список JavaScript Sorcery, чтобы ежемесячно погружаться в более волшебные советы и трюки JavaScript.

P.P.S. Если вам нужен APM для вашего приложения на Node.js, обратите внимание на AppSignal APM для Node.js.

Понравилась статья? Поделить с друзьями:
  • React hook form обработка ошибок
  • React wad ошибка
  • Realtek audio console microsoft store ошибка
  • Reached end of file while parsing java ошибка
  • Red alert 3 ошибка 5 0000065434