T
TanStack7mo ago
old-apricot

Double toasts on error

so when the getAcedmic years throw an error am getting two taosts same message, even after disabling retries
const { isPending, isError, data, error } = useQuery({
queryKey: ["academic-years"],
queryFn: getAcademicYears,
});

if (isError) {
toast.error(error.message);
}

const table = useReactTable({
columns,
data: data ?? [],
getCoreRowModel: getCoreRowModel(),
});
const { isPending, isError, data, error } = useQuery({
queryKey: ["academic-years"],
queryFn: getAcademicYears,
});

if (isError) {
toast.error(error.message);
}

const table = useReactTable({
columns,
data: data ?? [],
getCoreRowModel: getCoreRowModel(),
});
6 Replies
quickest-silver
quickest-silver7mo ago
You're triggering the toast during the render phase of the component, so the toast will be triggered every render. Back in v4 you could've used the onError callback to deal with one-off side-effects, now you'll have to use a useEffect, something like this:
const { isPending, data, error } = useQuery({
queryKey: ["academic-years"],
queryFn: getAcademicYears,
});

useEffect(() => {
if (error) {
toast.error(error.message);
}
}, [error]);
const { isPending, data, error } = useQuery({
queryKey: ["academic-years"],
queryFn: getAcademicYears,
});

useEffect(() => {
if (error) {
toast.error(error.message);
}
}, [error]);
If you have a retry happening in the background I suppose this could still cause multiple toasts since the error reference would change after each retry attempt and trigger the useEffect again. I'm only using toasts on mutations so don't have any hands-on experience with this particular use-case, not sure if there's a better pattern to handle it The queryResult object has a failureCount property, you might be able to combine the above useEffect approach with an additional if (error && failureCount === 1) { … } to get the desired effect. I think each query failure will increment that number so everything > 1 will be from the retries which you're not interested in for the toast
helpful-purple
helpful-purple7mo ago
Dominik has a few posts on this topic https://tkdodo.eu/blog/breaking-react-querys-api-on-purpose https://tkdodo.eu/blog/react-query-error-handling Sounds like a global error handler serves you best
old-apricot
old-apricotOP7mo ago
Thanks a lot for the article Am trying to avoiding use effect
quickest-silver
quickest-silver7mo ago
As of React 18 you can't put side-effects like that straight in render anymore and need to use useEffect due to how the render can be interrupted/restarted/paused and could trigger your side-effect multiple times. See https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react and the rest of the docs around it
In a concurrent render, this is not always the case. React may start rendering an update, pause in the middle, then continue later. It may even abandon an in-progress render altogether. React guarantees that the UI will appear consistent even if a render is interrupted. To do this, it waits to perform DOM mutations until the end, once the entire tree has been evaluated. With this capability, React can prepare new screens in the background without blocking the main thread. This means the UI can respond immediately to user input even if it’s in the middle of a large rendering task, creating a fluid user experience.
React v18.0 – React
The library for web and native user interfaces
quickest-silver
quickest-silver7mo ago
The only thing that can guarantee in this case that your side-effect (the toast) will only trigger once is a useEffect. It's definitely a good practice to avoid useEffect as much as possible (see for example this article) but in this instance it's the right (and only) tool for the job since the react-query error handlers are no longer available.
old-apricot
old-apricotOP7mo ago
Yeah I understand it batter now thanks a lot

Did you find this page helpful?