How to fix onError callback has been removed in v5, use useEffect or error boundary instead in React

ReactINTERMEDIATEMEDIUMUpdated Dec 10, 2025ยท 100 views

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" .
  1. Identify all useQuery hooks using callbacks
  2. Replace component-level callbacks with useEffect
  3. Use global QueryCache callbacks for centralized error handling
  4. Implement error boundaries for render-phase error handling
react โ€” error
# 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" .
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

  1. Upgrading from React Query v4 to v5 without updating query hooks to remove callback properties
  2. Following outdated React Query tutorials or documentation that use v4 API patterns
  3. Copying legacy code that uses onError/onSuccess callbacks into a v5 project
  4. 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"

1Identify all useQuery hooks using callbacks

Search your codebase for all instances of useQuery that use onError, onSuccess, or onSettled:

bash
# 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.

2Replace component-level callbacks with useEffect

For component-specific side effects (like showing toast notifications), use useEffect to watch the query state:

Before (v4):

typescript
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):

typescript
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).

3Use global QueryCache callbacks for centralized error handling

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:

typescript
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:

typescript
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.

4Implement error boundaries for render-phase error handling

For errors that should affect the UI rendering, use React Error Boundaries with the throwOnError option:

typescript
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:

typescript
const { data } = useQuery({
  queryKey: ['user'],
  queryFn: fetchUser,
  throwOnError: (error) => error.status >= 500, // Only throw server errors
});
5Update mutation hooks if needed

Note that useMutation still supports callbacks in v5. If you're using mutations, no changes are needed:

typescript
// 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:

typescript
// 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.

Official resources & further reading