Unfortunately, sometimes a server request fails and we need to display a helpful error message to the user. In this lesson we’ll handle a promise rejection so we can collect that error information, and we’ll also learn how we can best display manage the state of our request so we have a deterministic render method to ensure we always show the user the proper information based on the current state of our React component.
A common mistake people make is to create a state variable called isLoading
and set that to true
or false
. Instead, we’ll be using a status
variable which can be set to idle
, pending
, resolved
, or rejected
. You can learn more about why this is important from Stop using isLoading booleans.
I notice that you didn't use .catch for you promise in useEffect. Is this a style you prefer? Is there an advantage?
Yes, there is an advantage:
promise.then(
() => {
throw new Error('oh no')
},
() => { /* not called */ }
).catch(() => { /* called because the success handler failed */ })
In my example, I only want to catch errors with the promise itself, not with the success handler.
It is very nice that you show how to make an async request and handle errors from a React component. In my opinion, this kind of pattern is commonly used to interact with remote server and I wonder if there is any built-in component or third party component library to simplify this process? Thank you.
Take a look at react-query
@kent the course is really good and useful.
I want to share my comment how the source code can be better. The source code implementation is:
const [status, setStatus] = React.useState('idle')
The native React hook can be replaced with state machine: https://xstate.js.org/viz/
Kyle Shevlin has a great course: Introduction to State Machines Using XState
The state machine can be:
const fetchMachine = Machine({
id: 'fetch',
initial: 'idle',
context: {
retries: 0
},
states: {
idle: {
on: {
FETCH: 'loading'
}
},
loading: {
on: {
RESOLVE: 'success',
REJECT: 'failure'
}
},
success: {
type: 'final'
},
failure: {
on: {
RETRY: {
target: 'loading',
actions: assign({
retries: (context, event) => context.retries + 1
})
}
}
}
}
})
Yes, I think it is worth mentioning that this approach of handling the request status - is the state machine and basically every network request should be done using such an approach.
the new endpoint doesn't send an error status. update code to handle a null data.
if (status === 'rejected' || pokemon === null) { return 'Oh no...' }