Handling Fetching Errors in React

If you are working with our React integration you may need to show when a component failed to fetch data from the API. By default our useResource and useCollection hooks will throw the rejection, so you could use React Error Boundaries to handle those errors.

Let's imagine we have a User component which will fetch the user with an ID received as a prop and render the full name of that user.

import React from 'react';
import { useResource } from 'coreql';
function User({ id }) {
const { data: user } = useResource('users', id, { format: 'denormalized' });
return <h1>{user?.fullName}</h1>;
}
export default User;

Now let's create a parent component that will render the user, it will have a state to keep the ID and let the user of the app change it.

function App() {
const [id, setId] = React.useState(1);
return (
<>
<input
type="text"
value={id}
onChange={event => setId(event.target.value)}
/>
<SSRSuspense fallback={<p>Loading...</p>}>
<User id={id} />
</SSRSuspense>
</>
);
}

If we run that it will work without issues, we will see the loading state and then the data. Now let's imagine the user change the input value to a number who doesn't match any user in our API. The API will return a 404 and useResource will throw the error, we now need to wrap our component in an error boundary, you could create your boundary, but if you don't want to CoreQL expose one you could use.

import { ErrorBoundary } from 'coreql';

Now we could wrap our User component in that error boundary.

function App() {
const [id, setId] = React.useState(1);
return (
<>
<input
type="text"
value={id}
onChange={event => setId(event.target.value)}
/>
<ErrorBoundary fallback={({ error }) => <p>Error {error.message}</p>}>
<React.Suspense fallback={<p>Loading...</p>}>
<User id={id} />
</React.Suspense>
</ErrorBoundary>
</>
);
}

Now if the request to get the user failed it will be caught by the React error boundary and we will show a fallback. With this a handling loading state with Suspense we could move those states to the parent instead of the component, with Concurrent Mode we will be able to make this even faster.

Final Code

// user.js
import React from 'react';
import { useResource } from 'coreql';
function User({ id }) {
const { data: user } = useResource('users', id, { format: 'denormalized' });
return <h1>{user?.fullName}</h1>;
}
export default User;
// index.js
import React from 'react';
import { ErrorBoundary, SSRSuspense } from 'coreql';
import User from './user';
function App() {
const [id, setId] = React.useState(1);
return (
<>
<input
type="text"
value={id}
onChange={event => setId(event.target.value)}
/>
<ErrorBoundary fallback={({ error }) => <p>Error {error.message}</p>}>
<SSRSuspense fallback={<p>Loading...</p>}>
<User id={id} />
</SSRSuspense>
</ErrorBoundary>
</>
);
}