Error Handling
The React-query useErrorBoundary config property is set by default on the queryClient instance, making sure errors are thrown in the render phase and propagate to the nearest error boundary. A global "catch-all" error boundary has been placed
at root level of the component hierarchy, making sure all errors are handled by giving users the option to either cancel or
retry a specific action.
App.tsx
const App = () => {...const handleErrorReset = (args: any) => {queryClient.clear();setNavigationInitialState(args?.retry ? navigationState.current : undefined);};return (<ErrorBoundary FallbackComponent={StateError} onReset={handleErrorReset}><NavigationContainerinitialState={navigationInitialState}onStateChange={state => { navigationState.current = state }}>...</NavigationContainer></ErrorBoundary>);};}
Error flow#
As mentioned above, the errorBoundaryin App.tsx handles all errors thrown by react-query or React components.
Centralized error handling means developers do not have to worry about handling errors inside React components or
queries and mutations. Whenever an error occurs when fetching data, React Query throws an error, which is then catched by
the error boundary. Whithin each error response returned by the server is an error code used to generate the correct error message presented to user.
- Client requests data with useQuery or performs a mutation with useMutation
- Server returns an error
- React query throws an error
- Error boundary catches the error
- Snapshot navigation state
- The entire component tree is detached, and the error screen is presented
- The error code is extracted from the server response, and presented on the error screen
The user has several options in order to fix the problem:
User clicks "try again"
- Client resets the error boundary and clears the react query cache
- Client resets to the previous navigation state by using the stored snapshot
- The last screen is then rendered, and the data is refetched
User clicks "cancel" or "go back"
- Client resets the error boundary and clears the react query cache
- Client clears all navigation state
- The user is navigated back to the home screen
Error response#
Errors coming from the server contains an object in the response body with properties like status code, message, error type and more. If the server does not have a predefined description of the error, then the returned response body is empty.
ErrorResponse type definition (errorResponse.ts)
export type ErrorResponse = Error & {body?: {message: string;error: string;status: number;data?: { [k: string]: string };};};
Translations and error messages#
By storing error messages presented to the users on the client, instead of relying on the server, we can make sure they are translated based on the selected language. The error key used to display the correct error message is extracted from the error response. If the serves does not respond with a error code, then a generic error message is displayed to the user.
Error codes and translation tokens (errorMessages.ts):
| Error key | Translation tokens |
|---|---|
| TIMESLOT_LOCK_DEADLINE_SURPASSED | error.timeslot_lock_deadline_surpassed.title error.timeslot_lock_deadline_surpassed.description |
| GENERIC | error.generic.title error.generic.description |
Local errors#
It is of course possible to bypass the global error handling system, and instead handle errors locally if needed. Sometimes it is necessary to display an inline error to the users on the same screen that resulted in an error. One approach is to perform the necessary actions and state management inside the onError callback returned by the query or mutation itself.
const { data } = useQuery({queryKey: ['foo'],queryFn: fetchFoo,useErrorBoundary: false,onError: (error) => toast.error(`Something went wrong: ${error.message}`),});
Another approach is to use the error property returned by a query:
const { data, error } = useQuery({queryKey: ['foo'],queryFn: fetchFoo,useErrorBoundary: false,});if (error) {// Do something}
It is also possible to place the ErrorBoundary closer to the component in the component tree.
const ParentComponent = () => (<ErrorBoundary FallbackComponent={StateError} inline><ChildThatThrows /></ErrorBoundary>);
Limitations#
These are the limitations when using an error boundary, i.e. will not catch an error from:
- Event handlers
- Asynchronous code (ie. setTimeout)
- Errors thrown in the error boundary itself (only its children)
Previous
Dialogue Chat
Next