How to fix fetch did not throw or return a rejected promise. You must handle error throwing in your query function in React
React Query cannot detect errors from fetch API calls because fetch does not automatically throw errors on HTTP error status codes. You must manually check response.ok and throw errors in your queryFn.
Quick fix
Fixes fetch did not throw or return a rejected promise. You must handle error throwing in your query function
const { data, isError, error } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
});- Check response.ok and throw errors
- Include detailed error information
- Create a reusable fetch wrapper
- Handle error response bodies
const { data, isError, error } = useQuery({queryKey: ['user', userId],queryFn: async () => {const response = await fetch(`/api/users/${userId}`);if (!response.ok) {throw new Error('Network response was not ok');}return response.json();}});fetch did not throw or return a rejected promise. You must handle error throwing in your query function
What this error means
This error occurs when using TanStack Query (React Query) with the native fetch API. Unlike libraries like axios or graphql-request that automatically throw errors for unsuccessful HTTP calls, the fetch API does not throw errors by default for HTTP error status codes (4xx, 5xx). React Query requires query functions to either throw an error or return a rejected Promise to signal that something went wrong. When fetch returns a response with an error status code (like 404 or 500), it still resolves the Promise successfully with a Response object. This means React Query cannot detect the error and properly update the query state. The error message indicates that your queryFn completed successfully without throwing or rejecting, even though the HTTP request failed. This prevents React Query from triggering its error handling mechanisms and updating the error state properly.
Typical symptoms
- React Query shows success state even when API returns error status codes
- isError remains false despite failed HTTP requests
- error property is undefined even after receiving 4xx or 5xx responses
- Query appears to succeed but returns error response bodies
- Error boundaries do not catch failed API calls
- onError callbacks never trigger for failed requests
Common causes
- Using fetch API without checking response.ok property
- Not throwing errors in queryFn when HTTP status indicates failure
- Returning response directly without validation
- Missing error handling in async query functions
- Catching errors without re-throwing them in queryFn
How to fix "fetch did not throw or return a rejected promise. You must handle error throwing in your query function"
The most common solution is to check the response.ok property and throw an error if it's false:
const { data, isError, error } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
});This ensures React Query can properly detect and handle errors. The response.ok property is true for status codes in the 200-299 range.
For better error handling, include the status code and status text in your error:
const { data, isError, error } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(
`HTTP error! status: ${response.status} ${response.statusText}`
);
}
return response.json();
}
});This provides more context when debugging errors.
To avoid repeating error handling logic, create a wrapper function:
async function fetchWithErrorHandling(url: string, options?: RequestInit) {
const response = await fetch(url, options);
if (!response.ok) {
const errorBody = await response.text();
throw new Error(
`HTTP ${response.status}: ${errorBody || response.statusText}`
);
}
return response.json();
}
// Use in queries
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchWithErrorHandling(`/api/users/${userId}`)
});This centralizes error handling and makes it reusable across all queries.
If your API returns error details in the response body, extract them:
const { data, error } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
const errorData = await response.json().catch(() => null);
throw new Error(
errorData?.message || `HTTP error ${response.status}`
);
}
return response.json();
}
});
// Access error message
if (isError) {
console.log(error.message); // Shows API error message
}This allows you to display server-provided error messages to users.
Instead of throwing, you can return a rejected Promise:
const { data, isError } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
return Promise.reject({
status: response.status,
message: response.statusText
});
}
return response.json();
}
});Both throw and Promise.reject() work equally well for signaling errors to React Query.
Advanced notes
Re-throwing Caught Errors
If you catch errors during processing, you must re-throw them for React Query to detect them:
queryFn: async () => {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Fetch failed');
return response.json();
} catch (error) {
// Log for debugging
console.error('Query failed:', error);
// Re-throw so React Query sees it
throw error;
}
}Custom Error Objects
You can throw custom error objects with additional data:
class ApiError extends Error {
constructor(
public status: number,
public statusText: string,
public data?: any
) {
super(`API Error: ${status} ${statusText}`);
this.name = 'ApiError';
}
}
queryFn: async () => {
const response = await fetch('/api/data');
if (!response.ok) {
const data = await response.json().catch(() => null);
throw new ApiError(response.status, response.statusText, data);
}
return response.json();
}Global Error Handling
Configure default error handling for all queries using QueryClient:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: (failureCount, error) => {
// Don't retry on 4xx errors
if (error instanceof ApiError && error.status < 500) {
return false;
}
return failureCount < 3;
},
onError: (error) => {
// Global error handler
console.error('Query error:', error);
toast.error('Something went wrong');
}
}
}
});Comparison with Axios
Axios automatically throws for error status codes, so this pattern isn't needed:
// With axios - no manual error checking needed
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: () => axios.get(`/api/users/${userId}`).then(res => res.data)
});Consider using axios or a similar library if you want automatic error handling across your application.
Frequently asked questions
- What does "fetch did not throw or return a rejected promise. You must handle erro" mean?
- This error occurs when using TanStack Query (React Query) with the native fetch API. Unlike libraries like axios or graphql-request that automatically throw errors for unsuccessful HTTP calls, the fetch API does not throw errors by default for HTTP error status codes (4xx, 5xx). React Query requires query functions to either throw an error or return a rejected Promise to signal that something went wrong.
- How do I fix "fetch did not throw or return a rejected promise. You must handle erro"?
- React Query cannot detect errors from fetch API calls because fetch does not automatically throw errors on HTTP error status codes. You must manually check response.ok and throw errors in your queryFn.
- What causes "fetch did not throw or return a rejected promise. You must handle erro"?
- Using fetch API without checking response.ok property Not throwing errors in queryFn when HTTP status indicates failure Returning response directly without validation
- Is the "fetch did not throw or return a rejected promise. You must handle erro" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.