How to Fix Unhandled Promise Rejection Error in React and Async/Await
The "Unhandled Promise Rejection" (or Uncaught (in promise)) error occurs in React and JavaScript applications whenever a Promise is rejected—due to a network glitch, bad HTTP response, or runtime exception—without an attached .catch() block or an enclosing try...catch statement. In React apps, unhandled rejections can cause silent UI freezes, stale state, or component crashes in production.
Quick Diagnostics
Uncaught (in promise) Error in browser developer console during form submit or API fetch: Async function executed without wrapping await calls in a try...catch blockawait expressions inside try...catch blocks and manage error stateuseEffect hook: Promise launched inside an event handler or effect missing proper error handlingasync function inside useEffect and handle errors explicitlyfetch() API does not reject promises on HTTP error status codes (e.g., 404 or 500)response.ok before parsing JSON and throw an Error manually if falseStep-by-Step Solution
-
1
Step 1: Wrap async/await Calls in try...catch...finally Blocks
The primary cause of unhandled rejections is executing asynchronous logic without catching potential failures. Refactor your API handlers:
JAVASCRIPT// ❌ INCORRECT: If network fails or res.json() throws, the promise rejects unhandled const handleFetchData = async () => { const res = await fetch('/api/user-data'); const data = await res.json(); setUser(data); }; // ✅ CORRECT: Clean error interception and state management const handleFetchData = async () => { setLoading(true); setError(null); try { const res = await fetch('/api/user-data'); if (!res.ok) { throw new Error(`HTTP Error: ${res.status} ${res.statusText}`); } const data = await res.json(); setUser(data); } catch (err) { console.error('Failed to fetch user data:', err.message); setError(err.message || 'Connection error'); } finally { setLoading(false); } }; -
2
Step 2: Handle Promises Properly Inside useEffect
In React, callback functions passed to
useEffectcannot be directly marked asasyncbecause effects must return clean-up functions orundefined. Declare an internal async function:JSXimport React, { useState, useEffect } from 'react'; export const UserProfile = ({ userId }) => { const [user, setUser] = useState(null); const [error, setError] = useState(null); useEffect(() => { let isMounted = true; // Prevents state updates on unmounted components const fetchUserProfile = async () => { try { const response = await fetch(`/api/users/${userId}`); if (!response.ok) throw new Error('User profile not found'); const result = await response.json(); if (isMounted) { setUser(result); } } catch (err) { if (isMounted) { setError(err.message); } } }; fetchUserProfile(); return () => { isMounted = false; }; }, [userId]); if (error) return <div className="error-banner">Error: {error}</div>; if (!user) return <div>Loading...</div>; return <div>{user.name}</div>; }; -
3
Step 3: Register a Global Fallback for Unhandled Rejections
To prevent uncaught promise errors from polluting your application logs unnoticed in production, attach a fallback listener to the
windowobject:JAVASCRIPT// In index.js / main.jsx / App.jsx if (typeof window !== 'undefined') { window.addEventListener('unhandledrejection', (event) => { console.warn(`[Unhandled Rejection Captured]: ${event.reason}`); // Optional: Log error details to Sentry or monitoring service // event.preventDefault(); // Suppresses default browser console warning if needed }); }
Prevention Advice
Recommended security practices:
- Remember
fetch()Behavior: Nativefetch()only rejects on network failures (e.g., DNS lookup failure, connection refused), not on HTTP status 404 or 500. Always checkresponse.ok. - Consider Axios for Auto-Rejection: Libraries like
axiosautomatically reject promises for any HTTP response outside the 2xx status range. - Implement React Error Boundaries: Pair local async error handling with top-level React Error Boundaries to display graceful fallback UI components when unexpected errors occur.