Proper Error Handling in React Query

Just a guy who loves to write code and watch anime.
Search for a command to run...

Just a guy who loves to write code and watch anime.
No comments yet. Be the first to comment.
Qualities that make outstanding builders.

Bipedal vs quadrupedal: how many legs This is about the number of legs the creature walks on. Bipedal. Two legs. Humans, ostriches, T-rex, kangaroos, most fantasy humanoids. Quadrupedal. Four legs. Do

Intro You hear "lerp" and "smoothstep" everywhere in game dev. They sound like math jargon. They're not. Both are small tools that do the same job: smoothly move from one value to another. The problem

What is Kinematics Kinematics is the math field. It's the study of how things move without worrying about forces (which would be dynamics). FK and IK are the two branches: forward kinematics and inver

Intro Textures are usually the biggest cost in a 3D scene. Memory, bandwidth, and load time all get eaten by them. Resizing them is the obvious lever. There's more. This post is about the less obvious

function useRepos() {
return useQuery({
queryKey: ['repos'],
queryFn: async () => {
try {
const response = await fetch('/api/repos')
if (!response.ok) throw new Error('Failed')
return response.json()
} catch (error) {
console.log('Error:', error) // ❌ Swallows the error!
// React Query never knows an error occurred
}
}
})
}
// Result: status stays 'success', no retries, broken error handling
function useRepos() {
return useQuery({
queryKey: ['repos'],
queryFn: async () => {
const response = await fetch('/api/repos')
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`) // ✅ Throws to RQ
}
return response.json()
},
retry: 3, // Works because error propagates
retryDelay: 1000
})
}
When you catch and don't re-throw:
No retries → React Query doesn't know it failed
status stays 'success' → Component thinks everything is fine
No error boundaries → Can't use global error handling
Silent failures → Users never know something went wrong
const queryClient = new QueryClient({
defaultOptions: {
queries: {
throwOnError: (error, query) => {
// Only throw to Error Boundary if no cached data
return typeof query.state.data === 'undefined'
}
}
},
queryCache: new QueryCache({
onError: (error, query) => {
// Show toast if we have cached data (background refetch failed)
if (typeof query.state.data !== 'undefined') {
toast.error(`Background sync failed: ${error.message}`)
}
}
})
})
This pattern gives you:
Error Boundaries for initial load failures
Toast notifications for background failures
Automatic retries for transient issues
Global error handling without component coupling
The key insight: Let React Query own the error lifecycle, then hook into it where you need to.