How to fix onError callback has been removed in v5, use useEffect or error boundary instead in React
React Query v5 removed the onError, onSuccess, and onSettled callbacks from useQuery to improve predictability and align with React patterns. Developers must now use useEffect for component-level side effects or global QueryCache callbacks for centralized error handling.
Quick fix
Fixes onError callback has been removed in v5, use useEffect or error boundary instead
# Search for callback usage
grep -r "onError" --include="*.tsx" --include="*.ts" .
grep -r "onSuccess" --include="*.tsx" --include="*.ts" .
grep -r "onSettled" --include="*.tsx" --include="*.ts" .- Identify all useQuery hooks using callbacks
- Replace component-level callbacks with useEffect
- Use global QueryCache callbacks for centralized error handling
- Implement error boundaries for render-phase error handling
# Search for callback usagegrep -r "onError" --include="*.tsx" --include="*.ts" .grep -r "onSuccess" --include="*.tsx" --include="*.ts" .grep -r "onSettled" --include="*.tsx" --include="*.ts" .onError callback has been removed in v5, use useEffect or error boundary instead
What this error means
This error occurs when upgrading from React Query v4 to v5 (TanStack Query v5) and attempting to use the `onError`, `onSuccess`, or `onSettled` callbacks within the `useQuery` hook. These callbacks were intentionally removed in v5 as part of a major breaking change to improve the predictability and maintainability of side effects in data-fetching logic. The removal addresses several fundamental issues with the previous callback-based approach: callbacks were tied to fetches rather than state changes (meaning they wouldn't fire when data came from cache), they could run multiple times per query (once per component using the hook), and they encouraged imperative patterns that conflicted with React's declarative philosophy. The v5 migration pushes developers toward more predictable alternatives like useEffect and error boundaries. It's important to note that these callbacks remain available in `useMutation` - only `useQuery` and `QueryObserver` were affected by this change.
Typical symptoms
- TypeScript errors indicating onError, onSuccess, or onSettled are not valid properties on useQuery options
- Build failures or type errors when upgrading to React Query v5
- Side effects like toast notifications or error logging no longer trigger after query errors
- Components that previously handled errors through onError callback now fail silently
Common causes
- Upgrading from React Query v4 to v5 without updating query hooks to remove callback properties
- Following outdated React Query tutorials or documentation that use v4 API patterns
- Copying legacy code that uses onError/onSuccess callbacks into a v5 project
- Attempting to use mutation-style callbacks on query hooks after migration
How to fix "onError callback has been removed in v5, use useEffect or error boundary instead"
Search your codebase for all instances of useQuery that use onError, onSuccess, or onSettled:
# Search for callback usage
grep -r "onError" --include="*.tsx" --include="*.ts" .
grep -r "onSuccess" --include="*.tsx" --include="*.ts" .
grep -r "onSettled" --include="*.tsx" --include="*.ts" .Make a list of all affected files to systematically migrate.
For component-specific side effects (like showing toast notifications), use useEffect to watch the query state:
Before (v4):
const { data, error } = useQuery({
queryKey: ['user', userId],
queryFn: fetchUser,
onError: (error) => {
toast.error(`Failed to load user: ${error.message}`);
},
onSuccess: (data) => {
console.log('User loaded:', data);
},
});After (v5):
const { data, error, isError, isSuccess } = useQuery({
queryKey: ['user', userId],
queryFn: fetchUser,
});
useEffect(() => {
if (isError && error) {
toast.error(`Failed to load user: ${error.message}`);
}
}, [isError, error]);
useEffect(() => {
if (isSuccess && data) {
console.log('User loaded:', data);
}
}, [isSuccess, data]);Important: Be aware that if you call this custom hook from multiple components, each will register its own effect and could trigger duplicate side effects (like multiple toast notifications).
For application-wide error handling (like logging or global notifications), use the global callbacks on QueryCache. These run once per query regardless of how many components use it:
import { QueryClient, QueryCache } from '@tanstack/react-query';
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
// This runs once per query, not once per component
console.error('Query error:', error);
// Check query meta for custom behavior
if (query.meta?.showErrorToast) {
toast.error(`Error: ${error.message}`);
}
},
onSuccess: (data, query) => {
// Global success handling
console.log('Query succeeded:', query.queryKey);
},
}),
});Then use query meta to opt-in specific queries:
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: fetchUser,
meta: {
showErrorToast: true,
},
});This approach is fail-safe and ensures side effects run exactly once per query.
For errors that should affect the UI rendering, use React Error Boundaries with the throwOnError option:
import { QueryErrorResetBoundary } from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
<h2>Something went wrong:</h2>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)}
>
<UserProfile />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
function UserProfile() {
const { data } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
throwOnError: true, // Throw errors to nearest error boundary
});
return <div>{data.name}</div>;
}You can also use a function for conditional error throwing:
const { data } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
throwOnError: (error) => error.status >= 500, // Only throw server errors
});Note that useMutation still supports callbacks in v5. If you're using mutations, no changes are needed:
// This still works in v5
const mutation = useMutation({
mutationFn: updateUser,
onError: (error) => {
toast.error(error.message);
},
onSuccess: (data) => {
toast.success('User updated successfully');
},
});Only useQuery and QueryObserver callbacks were removed.
Advanced notes
Why callbacks were removed:
The React Query team made this breaking change deliberately after extensive community discussion. The key issues with callbacks included:
1. Inconsistent behavior with caching: When queries returned cached data (with staleTime configured), callbacks wouldn't fire because they were tied to fetches, not state changes. This created confusion about when side effects would run.
2. Multiple invocations: Each component calling a custom hook with useQuery would trigger its own callback execution, leading to duplicate side effects (like showing multiple error notifications).
3. Closure problems: Callbacks could close over stale component state, leading to subtle bugs when the callback referenced outdated values.
4. React patterns misalignment: The imperative callback approach conflicted with React's declarative philosophy and the recommended patterns for handling side effects.
Performance considerations:
Using useEffect for error handling does introduce an additional render cycle - the query first updates with the error state, then the effect runs. For most applications this is negligible, but if you're concerned about performance, global QueryCache callbacks don't have this issue since they run outside the component render cycle.
Gradual migration strategy:
If you have a large codebase, consider creating a compatibility layer during migration:
// Temporary compatibility wrapper
function useQueryV4Compatible(options) {
const result = useQuery({
queryKey: options.queryKey,
queryFn: options.queryFn,
...options,
});
useEffect(() => {
if (options.onError && result.isError) {
options.onError(result.error);
}
}, [result.isError, result.error]);
return result;
}This allows gradual migration without breaking existing code, though you should ultimately remove this wrapper and use the recommended v5 patterns.
Frequently asked questions
- What does "onError callback has been removed in v5, use useEffect or error bounda" mean?
- This error occurs when upgrading from React Query v4 to v5 (TanStack Query v5) and attempting to use the onError, onSuccess, or onSettled callbacks within the useQuery hook. These callbacks were intentionally removed in v5 as part of a major breaking change to improve the predictability and maintainability of side effects in data-fetching logic. The removal addresses several fundamental issues with the previous callback-based approach: callbacks were tied to fetches rather than state changes (meaning they wouldn't fire when data came from cache), they could run multiple times per query (once per component using the hook), and they encouraged imperative patterns that conflicted with React's declarative philosophy.
- How do I fix "onError callback has been removed in v5, use useEffect or error bounda"?
- React Query v5 removed the onError, onSuccess, and onSettled callbacks from useQuery to improve predictability and align with React patterns. Developers must now use useEffect for component-level side effects or global QueryCache callbacks for centralized error handling.
- What causes "onError callback has been removed in v5, use useEffect or error bounda"?
- Upgrading from React Query v4 to v5 without updating query hooks to remove callback properties Following outdated React Query tutorials or documentation that use v4 API patterns Copying legacy code that uses onError/onSuccess callbacks into a v5 project
- Is the "onError callback has been removed in v5, use useEffect or error bounda" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.