On this page of the React docs:
https://reactjs.org/docs/faq-ajax.html
A code comment says...
Note: it's important to handle errors here instead of a catch() block so that we don't swallow exceptions from actual bugs in components.
...about handling errors in the second argument to the second .then
after fetch
. The complete snippet from the docs is:
useEffect(() => {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
It doesn't elaborate on this advice, and I've seen many examples of working React code using catch
to handle errors from API calls. I've tried Googling but can't find any clarification.
Could someone please give me a more detailed explanation as to why I shouldn't use catch
to deal with errors that arise when making a fetch
API call in a useEffect
hook?
Or are there some situations when it's ok to do so, but others when it's not?
What is meant by "swallow exceptions [...] in components" ?
Does this rule/guideline apply to all of React, or just to API calls?
Or just to the useEffect
hook or componentDidMount
lifecycle method?
Error handling with useEffect is just another state, hence another useState hook. We set the error state, when an error occurs. This is usually done in a try/catch statement, when working with async/await.
Error boundaries were introduced in React 16 as a way to catch and handle JavaScript errors that occur in the UI parts of our component. So error boundaries only catch errors that occur in a lifecycle method, render method, and inside Hooks like useEffect .
Step-1 Create a class which extends React component and passes the props inside it. Step-2 Now, add componentDidCatch() method which allows you to catch error in the components below them in the tree. Step-3 Next add render() method, which is responsible for how the component should be rendered.
catch deals with imperative code while error boundaries*deal with declarative code. Imperative programming is how you do something and declarative programming is what you do. With error boundary, if there is an error, you can trigger a fallback UI; whereas, with try… catch, you can catch errors in your code.
How To Call Web APIs with the useEffect Hook in React 1 Creating a Project and a Local API. In this step, you’ll create a local REST API using JSON server, which you will use as a test data ... 2 Fetching Data from an API with useEffect. In this step, you’ll fetch a list of groceries using the useEffect Hook. ... 3 Sending Data to an API. ...
Error Handling in React Hooks 1 Error Handling Use Case Scenario. In this article, we will be creating a simple utility to divide 2 values, there might be a scenario, where the user might try to ... 2 Throwing Error From the React Component. ... 3 Tracking Error Situation in case of Error. ... 4 Getting it all Together. ... 5 Wrap Up:
APIs are the primary way for applications to programmatically communicate with servers to provide users with real-time data and to save user changes. In React applications, you will use APIs to load user preferences, display user information, fetch configuration or security information, and save application state changes.
All you’ll ever need again to handle your API errors is a top-level component that reads the current location’s state and reacts accordingly, coupled with any sort of central “api module” that can modify the history.
Everything I can find on this seems to link back to this github issue circa 2016. I'll quote verbatim from there since it doesn't appear to have been covered on Stack Overflow before and it explains things pretty thoroughly:
.then(() => {
this.setState({ loaded: true })
})
.catch(()=> {
console.log('Swallowed!')
});
Your
catch()
handler is going to catch any error thrown in thethen()
chain before it, including the one caused by arender()
due to asetState()
call.Even if you don't use
setState
directly, you may have the same problem if some other code you call uses it (for example, a Reduxdispatch()
).If you don’t want to catch errors resulting from
setState()
, and want to only catch network failures (let’s imagine yourPromise.resolve()
is actually afetch()
), you want to use the secondthen()
argument instead:
componentDidMount() {
Promise.resolve()
.then(() => {
this.setState({ loaded: true })
}, (err) => {
console.log('An error occurred (but not in setState!)', err);
});
}
In this case, unless you
catch()
later in the chain, the error inrender()
will be uncaught and, with a good Promise polyfill (or with native Promises in Chrome and maybe other browsers), displayed.
Edit: following the answer from @Martin I went and tested this, and I can confirm that this no longer appears to be a relevant concern. Render errors from setState
are not caught in any version of React from v16.0 onwards, and since useState
was only introuduced in v16.8, it doesn't seem possible that this could ever have been an issue for hooks.
Here is a codesandbox which demonstrates the original issue in the older versions of React.
It is a copy-and-paste error of the example's author.
The first example uses the component state of a class based component: this.setState()
causes a synchronous re-render (or at least this was the case with react v15.0 in 2016, not sure if it holds true today). Therefore the warning comment and the use of the second arg to .then(onResolve, onReject)
is justified.
The second example uses the useState
hook of a function component: setState
causes no synchronous re-render only an asynchronous re-render. Therefore the warning comment and the use of the second arg to .then(onResolve, onReject)
is not justified.
To illustrate this: with useState
hooks you can call multiple updaters and they all will only take effect in one re-render.
const [a, setA] = useState();
const [b, setB] = useState();
const [c, setC] = useState();
const updateState = () => {
setA('foo');
setB('bar');
setC('buz');
// will only cause one single re-render
}
and therefore its perfectly fine to use catch
useEffect(() => {
fetch("https://api.example.com/items")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
}
)
.catch(
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With