Introduction
The aim of this post is to outline the possibilities of handling errors in applications with react-query and present you different approaches to it as well as provide an example of usage.
Agenda
- React query
- Type guards
- Global handling with a query client
- Custom wrapper for mutations
- Providing an example of usage
React query
Let’s start by checking the documentation on website https://tanstack.com/query/latest/docs/react/overview where we should find a lot of useful information about features of this library. In our example only the mutations will be used for errors handling, because the query api will be rebuilt in next version of react-query. To find more information about it you should check out this post on Dominic blog https://tkdodo.eu/blog/breaking-react-querys-api-on-purpose who is one of maintainers this library.
Type guards
The purpose of a type guard is to check the type of an object, so let's create a type guard for the error model and functions for handling errors.
type ApiError = {
type: string;
title: string;
status: number;
detail: string;
instance: string;
};
function isApiErrorResponse(res: any): res is ApiError {
return (
res &&
"type" in res &&
"title" in res &&
"status" in res &&
"detail" in res &&
"instance" in res
);
}
export const handleErrorMessage = (error: unknown) => {
if (!axios.isAxiosError(error)) {
return "Unknown error";
}
if (!error.response) {
return error.message;
}
if (!isApiErrorResponse(error.response.data)) {
return error.message;
}
return error.response.data.detail;
};
export const handleErrorCode = (error: unknown) => {
if (!axios.isAxiosError(error)) {
throw Error("Unknown error");
}
if (!error.response) {
return error.status;
}
if (!isApiErrorResponse(error.response.data)) {
return error.response.status;
}
return error.response.data.status;
};
In above code we should pay attention to the isApiErrorResponse function which is a type guard. It has a specific syntax according to which a type guard should be created:
- it must be defined with function keyword
- its parameter type must be any
- its parameter type must be predicate (res is ApiError)
- we must check if all the properties are included in the parameter
Global handling with a query client
Let's create global error handling with using the query client in the App.tsx component
const App = () => {
const [queryClient] = useState(
() =>
new QueryClient({
mutationCache: new MutationCache({
onError: (error, _variables, _context, mutation) => {
if (mutation.options.onError) return;
const errorMessage = handleErrorMessage(error);
toast.error(errorMessage);
},
}),
})
);
return (
<QueryClientProvider client={queryClient}>
// Other elements
</QueryClientProvider>
);
};
export default App;
In above code we should pay attention to the queryClient definition, because in the documentation the library authors recommended define the queryClient above the component to create only one instance of the queryClient. However, this definition of the queryClient give us the same effect that is recommended by the authors of the react-query library. Another interesting elements is using of the mutationCache where we can define the global error handling, but first condition give us option to handle error differently in our particular mutation.