React вывести сообщение об ошибке

Cover image for How to display error messages in a React application

API calls may return errors, learn how to deal with them

In React we often have to work with external APIs. We typically grab data from a remote server and we display it in our application.

We saw that one very popular library for making http requests to remote servers is Axios. Axios lets us use methods like get(), post(), and others that call the corresponding http methods that deal with getting, posting, updating and deleting data from an API.

One good place where we want to put Axios calls is inside the componentDidMount() function of our class components.
componentDidMount() gets called by React automatically when the component mounts in our application. If we place the call to Axios in there, it will be called at the appropriate moment and the data retrieved will be available to the component state, ready to be displayed.

Possible API errors

Not all calls to external APIs are successful, though. In fact, it’s very possible that a remote server is down or some other blockage prevents the data we are looking for to be accessed.

In these cases, Axios will return an error. It’s common practice to notify the user that an error has occurred by triggering some kind of notification like displaying an error message in our web page.

How do we display error messages?

Let’s say we want to display an error message at the top of our view when something bad happens. In order to display the message we need to have the message sitting ready in our component state.
Let’s add an errorMessage property to our state object with the value of an empty string as the initial state.

state = {
  items: [],
  errorMessage: ''
}

We place our Axios call inside componentDidMount() and when the call is successful, we set the state to the value returned in the API response.

componentDidMount() {
  axios.get('http://localhost:3333/items')
    .then(response => this.setState({items: response.data}))
    .catch(err => { console.log(err) })
}

But when there is an error, the data won’t be available inside then(), and the catch() method will be called instead. The error object returned by the API will be passed in there.
At this point, what we need to do is grab the error and update the errorMessage property in our state using setState().

In the code below, I show this operation. In the catch branch I call setState() with an object that updates errorMessage with whatever error is returned by the API.

componentDidMount() {
  axios.get('http://localhost:3333/items')
    .then(response => this.setState({items: response.data}))
    .catch(err => { 
      this.setState({errorMessage: err.message});
    })
}

Now that we have the error in our state all we have to do is display it at the
top of our web page. How do we do that?

Display the error

There are many ways to do it but we like to create a conditional statement to
display the error. The conditional statement basically needs to say:

«if we have an errorMessage on the state, display an h3 element with the errorMessage value. However, if errorMessage is empty, don’t display anything.»

To translate this if condition into code we could use a plain old if statement, but we can also use a fancy way of doing it.

We use the shortcut operator &&.

The && operator is placed in the middle of a statement.

  • It first evaluates the left side of the statement.
  • If the left side is true, then the right side of the statement is executed.
  • If the left side is not true, && will not do anything with the right side.

In the code below we use the && operator to display the error message only if the errorMessage property on the state is not empty:

{ this.state.errorMessage &&
  <h3 className="error"> { this.state.errorMessage } </h3> }

This is saying: if this.state.errorMessage is true, display the error message.

Remember, we need to enclose this statement in brackets because we are writing Javascript code inside JSX.

In summary

  • API calls to external resources can get stuck and return errors instead of the expected data.
  • In this case we catch the error and we display it in our application, so the user knows something went wrong.
  • We display the error using a conditional statement that shows the error only if it exists.

As you can see, it’s very easy to write code that display error messages inside our React application.


I write daily about web development. If you like this article, feel free to share it with your friends and colleagues.

You can receive articles like this in your inbox by subscribing to my newsletter.

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

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

Привет, когда разрабатываем любой проект на 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.

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

could you please tell me how to show error message in react js when http request send ?

I make a service in the nodejs where I am sending 400 status with error message . I want to show this error message on frontend.

app.get('/a',(req,res)=>{
    res.status(400).send({message:" some reason error message"})
})

Now I want to show this error message on frontend .on catch I will not get this message.

try {
            const r = await axios.get('http://localhost:3002/a');
} catch (e) {
            console.log('===============================================')
            console.log(e)
            console.log(e.data)
            hideLoading();
            setErrorMessage(e.message);
            showErrorPopUp();
        }

on catch i will not get this message.getting on stack of error
[![enter image description here][1]][1]

enter image description here

Suman Kundu's user avatar

asked Aug 21, 2019 at 2:39

user944513's user avatar

user944513user944513

12.1k46 gold badges165 silver badges313 bronze badges

It’s better to respond with a JSON in this particular case from the server:

app.get('/a',(req,res) => {
    res.status(400).json({message:"some reason error message"})
})

So in the client, you can read from error.response easily

try {
  const r = await axios.get('http://localhost:3002/a');
} catch (e) {
  if (e.response && e.response.data) {
    console.log(e.response.data.message) // some reason error message
  }
}

Read more about handling caught errors in axios here

answered Aug 21, 2019 at 2:53

Suman Kundu's user avatar

That’s a very subjective question. You might need to use some middleware to handle async actions in a better way like redux-saga or redux-thunk.

The approach would be define a error state in your store. And, when you get an error update the state, dispatching an action.

And, in your component (container), you need to have an observer to get the updated error state.

try {
  const r = await axios.get('http://localhost:3002/a');
} catch (e) {
  if (e.response && e.response.data) {
    // Dispatch an action here
    console.log(e.response.data.message) // some reason error message
  }
}

For reference, there is a very basic and good tutorial by Dan.
https://egghead.io/lessons/javascript-redux-displaying-error-messages

answered Aug 21, 2019 at 3:06

Ashmah's user avatar

AshmahAshmah

8402 gold badges8 silver badges17 bronze badges

Axios have validateStatus in request-config where you can whitelist your status

https://github.com/axios/axios#request-config

// validateStatus defines whether to resolve or reject the promise
for a given // HTTP response status code. If validateStatus
returns true (or is set to null // or undefined), the promise
will be resolved; otherwise, the promise will be // rejected.

axios
  .get("<URL>",{validateStatus: function (status) {
    return (status >= 200 && status < 300) || status==400;
  }})
  .then(function(response) {
    // handle success;
  })
  .catch(function(response) {
    // handle error
  })
  .finally(function(error) {
    // always executed
  }); ```

answered Aug 21, 2019 at 4:41

MKR's user avatar

Simple, straightforward method

One way to display error messages is to have a state that stores them.

Let’s call this state errorMessage:

const [errorMessage, setErrorMessage] = useState('');

Now, whenever we encounter an error, we just update the errorMessage state:

setErrorMessage('Example error message!');

Then, display the error message using React conditional rendering. To keep things simple, we can just write an inline conditional statement:

{errorMessage && (
  <p className="error"> {errorMessage} </p>
)}

Now, let’s put it all together. Here we have a button that, once clicked, displays the error message:

Обрабатываем ошибки в 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

Возможно, вам также будет интересно:

  • Rdr 2 ошибка игры перезапустите систему
  • Rdr 2 ошибка игра уже запущена
  • Rdr 2 ошибка активации при запуске
  • Rdr 2 ошибка активации на пиратке
  • Rdr 2 ошибка активации social club

  • Понравилась статья? Поделить с друзьями:
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии