This React Query error occurs when a query function encounters an error but fails to properly signal it by throwing an error or returning a rejected promise. Without this signal, React Query cannot track the error state.
This error message indicates that your query function (queryFn) in React Query/TanStack Query encountered a problem, but the function didn't properly communicate this failure to React Query. For React Query to recognize that a query has failed, the query function must either throw an error or return a rejected Promise. React Query uses promises as the foundation for handling both successful data fetching and error states. When a query function completes successfully, it should resolve with data. When it encounters an error, it must reject (either by throwing or returning a rejected Promise) so React Query can update the query state accordingly. This error typically appears in the console when React Query detects that something went wrong, but your query function returned a resolved promise instead of properly signaling the error. This leaves React Query unable to transition the query into an error state, preventing error handling callbacks and UI error states from working correctly.
If you have a try-catch block in your queryFn for logging purposes, you must re-throw the error:
// ❌ Bad: Catches error but doesn't re-throw
const queryFn = async () => {
try {
const response = await fetch('/api/data');
return response.json();
} catch (error) {
console.error('Error fetching data:', error);
// Error is swallowed here - React Query never sees it
}
};
// ✅ Good: Re-throws the error
const queryFn = async () => {
try {
const response = await fetch('/api/data');
return response.json();
} catch (error) {
console.error('Error fetching data:', error);
throw error; // Re-throw so React Query can handle it
}
};The fetch API does not automatically throw errors for HTTP error status codes (4xx, 5xx). You must check response.ok and throw manually:
// ❌ Bad: fetch doesn't throw on HTTP errors
const queryFn = async () => {
const response = await fetch('/api/users');
return response.json(); // This succeeds even if status is 404 or 500
};
// ✅ Good: Check response.ok and throw
const queryFn = async () => {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
};
// ✅ Also good: Extract error message from response
const queryFn = async () => {
const response = await fetch('/api/users');
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Request failed');
}
return response.json();
};Consider using HTTP client libraries that automatically reject promises for error status codes:
// Using axios (automatically throws on error status codes)
import axios from 'axios';
const queryFn = async () => {
const { data } = await axios.get('/api/users');
return data;
// No need to check status - axios throws automatically
};
// Using ky (also throws automatically)
import ky from 'ky';
const queryFn = async () => {
return await ky.get('/api/users').json();
// Automatically throws on 4xx/5xx responses
};If you must use fetch, create a wrapper function:
async function fetchWithErrorHandling(url: string, options?: RequestInit) {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
}
// Use in queryFn
const queryFn = async () => {
const response = await fetchWithErrorHandling('/api/users');
return response.json();
};Make sure your useQuery is properly configured to handle errors:
import { useQuery } from '@tanstack/react-query';
function UserList() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Failed to fetch users');
}
return response.json();
},
retry: 2, // React Query will retry failed queries
});
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error: {error.message}</div>;
return <div>{/* Render data */}</div>;
}Note: In React Query v5, the onError callback was removed from useQuery. Use QueryCache for global error handling:
import { QueryClient, QueryCache } from '@tanstack/react-query';
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
console.error('Query error:', error, query.queryKey);
},
}),
});Promise Rejection vs Throwing:
Both throw new Error() and return Promise.reject(new Error()) are valid ways to signal errors to React Query. In async functions, throwing is more idiomatic and readable.
Error Handling with Promise.all:
When using Promise.all in a queryFn, if any promise rejects, the entire operation rejects automatically, which is what React Query expects. However, be aware that Promise.all fails fast - the first rejection stops everything.
Custom Error Classes:
You can throw custom error classes to add additional context:
class ApiError extends Error {
constructor(public status: number, message: string) {
super(message);
this.name = 'ApiError';
}
}
const queryFn = async () => {
const response = await fetch('/api/data');
if (!response.ok) {
throw new ApiError(response.status, 'API request failed');
}
return response.json();
};Retry Behavior:
React Query retries failed queries 3 times by default. If your error shouldn't be retried (like 401 Unauthorized), you can configure this:
useQuery({
queryKey: ['data'],
queryFn: myQueryFn,
retry: (failureCount, error) => {
// Don't retry on 401 or 403
if (error.status === 401 || error.status === 403) {
return false;
}
return failureCount < 3;
}
});TypeScript Type Safety:
Type your error properly for better IDE support:
const { error } = useQuery<User[], Error>({
queryKey: ['users'],
queryFn: fetchUsers
});
if (error) {
console.log(error.message); // TypeScript knows this is an Error
}React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite