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.
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]
asked Aug 21, 2019 at 2:39
user944513user944513
12.1k47 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
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
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. IfvalidateStatus
returnstrue
(or is set tonull
// orundefined
), 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
Время на прочтение
4 мин
Количество просмотров 7.9K
Привет, когда разрабатываем любой проект на 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.
А на этом пост заканчивается. Поделитесь своими мыслями в комментариях. И не забывайте, всегда продолжайте учиться!
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.
Классический метод «Try and Catch» в React
Если вы использовали JavaScript, вам, вероятно, приходилось писать инструкцию «try and catch». Чтобы убедиться в этом, посмотрите:
try {
somethingBadMightHappen();
} catch (error) {
console.error("Something bad happened");
console.error(error);
Это отличный инструмент для выявления неправильного кода и обеспечения того, чтобы наше приложение не сломалось. Чтобы быть более реалистичным и максимально приближенным к миру React, давайте посмотрим пример того, как вы будете использовать это в своем приложении:
const fetchData = async () => {
try {
return await fetch("https://some-url-that-might-fail.com");
} catch (error) {
console.error(error); // You might send an exception to your error tracker like AppSignal
return error;
}
При выполнении сетевых вызовов в React обычно используют инструкцию try...catch
. Но почему? К сожалению, try...catch
работает только с императивным кодом, но не работает с декларативным, таким как JSX, который пишут в компонентах. Вот почему вы не видите массивной упаковки try...catch
всего нашего приложения. Это просто не сработает.
Итак, что делать? В React 16 появилась новая концепция — границы ошибок React. Давайте разберемся, что это такое.
Границы ошибок React
Прежде чем мы перейдем к границам ошибок, давайте сначала посмотрим, почему они необходимы. Представьте, что у вас есть такой компонент:
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>;
};
export default CrashableComponent
Если вы попытаетесь отобразить этот компонент где-нибудь, вы получите ошибку, подобную этой:
Мало того, вся страница будет пустой, и пользователь не сможет ничего делать или видеть. Но что произошло? Мы попытались получить доступ к свойству iDontExist.prop, которого не существует (мы не передаем его компоненту). Это банальный пример, но он показывает, что мы не можем поймать эти ошибки try...catch
с помощью инструкции.
Весь этот эксперимент подводит нас к границам ошибок. Границы ошибок — это компоненты React, которые улавливают ошибки JavaScript в любом месте своего дочернего дерева компонентов. Затем они регистрируют эти обнаруженные ошибки и отображают резервный пользовательский интерфейс вместо дерева компонентов, которое разбилось. Границы ошибок улавливают ошибки во время рендеринга, в методах жизненного цикла и в конструкторах всего дерева под ними.
Граница ошибки — это классовый компонент, который определяет один (или оба) из методов жизненного цикла static getDerivedStateFromError()
или componentDidCatch().
static getDerivedStateFromError()
отображает резервный пользовательский интерфейс после возникновения ошибки. componentDidCatch()
можно передавать информацию об ошибках вашему поставщику услуг (например, AppSignal) или в консоль браузера.
Вот пример того, как информация об ошибке React выглядит в «списке проблем» AppSignal:
Давайте посмотрим на типичный компонент границы ошибки:
import { Component } from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return (
<div>
<p>Something went wrong ????</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary
Мы можем использовать ErrorBoundary
примерно так:
<ErrorBoundary>
<CrashableComponent />
</ErrorBoundary
Теперь, когда мы открываем наше приложение, мы получим рабочее приложение с текстом:
Это именно то, чего мы хотим. Мы хотим, чтобы наше приложение оставалось функциональным при возникновении ошибки. Но также хотим информировать пользователя (и нашу службу отслеживания ошибок) об ошибке.
Помните, что использование границ ошибок не панацея. Границы ошибок не распознают ошибки для:
-
Обработчики событий.
-
Асинхронный код (например,
setTimeout
, илиrequestAnimationFrame
Callbacks). -
Server-side rendering.
-
Ошибки, которые возникают в самой границе ошибки (а не в ее дочерних элементах).
В этих случаях всё равно нужно использовать try...catch
. И так, давайте продолжим и покажем, как вы можете это сделать.
Перехват ошибок в обработчиках событий
Как упоминалось ранее, границы ошибок не могут нам помочь, когда ошибка выдается внутри обработчика событий. Давайте посмотрим, как можно с ними справиться. Ниже приведен небольшой компонент кнопки, который выдает ошибку при нажатии на него:
import { useState } from "react";
const CrashableButton = () => {
const [error, setError] = useState(null);
const handleClick = () => {
try {
throw Error("Oh no :(");
} catch (error) {
setError(error);
}
};
if (error) {
return <span>Caught an error.</span>;
}
return <button onClick={handleClick}>Click Me To Throw Error</button>;
};
export default CrashableButton
Обратите внимание, что у нас есть блок try and catch
внутри handleClick
, который гарантирует, что наша ошибка будет обнаружена. Если вы отобразите компонент и попытаетесь щелкнуть по нему, это произойдет:
Нужно делать то же самое в других случаях, например, в вызовах setTimeout
Перехват ошибок в вызовах setTimeout
Представьте, что у нас есть аналогичный компонент button, но он вызывает setTimeout
при нажатии. Вот как это выглядит:
import { useState } from "react";
const SetTimeoutButton = () => {
const [error, setError] = useState(null);
const handleClick = () => {
setTimeout(() => {
try {
throw Error("Oh no, an error :(");
} catch (error) {
setError(error);
}
}, 1000);
};
if (error) {
return <span>Caught a delayed error.</span>;
}
return (
<button onClick={handleClick}>Click Me To Throw a Delayed Error</button>
);
};
export default SetTimeoutButton
Через 1000 миллисекунд callback setTimeout
выдаст ошибку. К счастью, мы включаем эту логику обратного вызова в try...catch
и в компонент setError
. Таким образом, трассировка стека не отображается в консоли браузера. Кроме того, мы сообщаем об ошибке пользователю. Вот как это выглядит в приложении:
Таким образом, мы запустили страницы приложения, несмотря на то, что ошибки появляются повсюду в фоновом режиме. Но есть ли более простой способ обработки ошибок без написания пользовательских границ ошибок? Вы можете поспорить, что есть, и, конечно же, он поставляется в виде пакета JavaScript. Позвольте мне познакомить вас с react-error-boundary.
react-error-boundary пакет JavaScript
Вы можете вставить эту библиотеку в свой package.json быстрее, чем когда-либо, с:
npm install --save react-error-boundary
Теперь вы готовы использовать его. Помните компонент ErrorBoundary
, который мы создали? Вы можете забыть об этом, потому что этот пакет экспортирует свои собственные. Вот как это использовать:
import { ErrorBoundary } from "react-error-boundary";
import CrashableComponent from "./CrashableComponent";
const FancyDependencyErrorHandling = () => {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error) => {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
console.error(error);
}}
>
<CrashableComponent />
</ErrorBoundary>
);
};
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong ????</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
export default FancyDependencyErrorHandling
В этом примере визуализируем то же CrashableComponent
, но на этот раз мы используем компонент ErrorBoundary
из библиотеки react-error-boundary. Он делает то же самое, что и наш пользовательский, за исключением того, что он получает FallbackComponent
и обработчик функции onError
. Результат тот же, что и с нашим пользовательским компонентом ErrorBoundary
, за исключением того, что вам не нужно беспокоиться о его обслуживании, поскольку вы используете внешний пакет.
Одна из замечательных особенностей этого пакета заключается в том, что вы можете легко обернуть свои функциональные компоненты в компонент withErrorBoundary
более высокого порядка (HOC). Вот как это выглядит:
import { withErrorBoundary } from "react-error-boundary";
const CrashableComponent = (props) => {
return <span>{props.iDontExist.prop}</span>;
};
export default withErrorBoundary(CrashableComponent, {
FallbackComponent: () => <span>Oh no :(</span>,
});
Хорошо, теперь вы можете записывать все те ошибки, которые вас беспокоят.
Но, возможно, вы не хотите, чтобы в вашем проекте была другая зависимость. Давайте посмотрим, как это можно сделать самостоятельно.
Используя свои собственные границы React
Похожего, если не точно такого же эффекта можно достичь с помощью react-error-boundary
. Мы уже разбирали кастомный ErrorBoundary
компонент, но предлагаю его улучшить.
import { Component } from "react";
export default class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return {
hasError: true,
error,
};
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service like AppSignal
// logErrorToMyService(error, errorInfo);
}
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />;
}
return this.props.children;
}
}
const ErrorFallback = ({ error }) => (
<div>
<p>Something went wrong ????</p>
{error.message && <span>Here's the error: {error.message}</span>}
</div>
);
const errorBoundary = (WrappedComponent) => {
return class extends ErrorBoundary {
render() {
const { hasError, error } = this.state;
if (hasError) {
// You can render any custom fallback UI
return <ErrorFallback error={error} />;
}
return <WrappedComponent {...this.props} />;
}
};
};
export { errorBoundary };
У вас получились ErrorBoundary
и HOC errorBoundary
, которые вы можете использовать во всем приложении. Их можно масштабировать и видоизменять. Вы можете сделать так, чтобы они получали индивидуальные fallback компоненты для кастомизации способов восстановления после каждой ошибки. Ещё можно настроить получение onError, и потом вызывать его внутриcomponentDidCatch
. Возможности не ограничены.
Одно могу сказать точно — эти взаимосвязи не нужны в конце концов. Уверен, написание собственного error boundary
даст ощущение успеха, сможете лучше их понимать. Ну и кто знает, может, придут какие-то интересные идеи в голову, пока вы экспериментируете с кастомизацией.
Резюмируем:
-
Границы ошибок React отлично подходят для обнаружения ошибок в декларативном коде (например, внутри дерева дочерних компонентов).
-
Для других случаев необходимо использовать инструкцию
try...catch
(например, асинхронные вызовы, такие какsetTimeout
обработчики событий, рендеринг на стороне сервера и ошибки, возникающие в самой границе ошибки). -
Подобная библиотека
react-error-boundary
помогает писать меньше кода. -
Вы также можете запустить свою собственную границу ошибок и настроить ее так, как хотите.