How to fix Hook returned an unexpected error in React

ReactINTERMEDIATEHIGHUpdated Dec 8, 2025· 130 views

This error occurs when a React Hook throws an error that isn't properly caught and handled, often due to async operations, improper error boundaries, or errors in custom hooks that propagate unexpectedly.

Quick fix

Fixes Hook returned an unexpected error

// Before - error not caught
useEffect(() => {
  const fetchData = async () => {
    const response = await fetch('/api/data');
    const data = await response.json();
    setData(data);
  };
  fetchData();
}, []);

// After - error properly caught
useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await fetch('/api/data');
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      setError(error.message);
      console.error('Failed to fetch data:', error);
    }
  };
  fetchData();
}, []);
  1. Add try-catch blocks to async hook operations
  2. Implement error state management in custom hooks
  3. Install and configure react-error-boundary for async errors
  4. Prevent state updates after unmount
react — error
// Before - error not caught
useEffect(() => {
const fetchData = async () => {
const response = await fetch('/api/data');
const data = await response.json();
setData(data);
};
fetchData();
}, []);
// After - error properly caught
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setData(data);
} catch (error) {
setError(error.message);
console.error('Failed to fetch data:', error);
}
};
fetchData();
}, []);
Hook returned an unexpected error

What this error means

The "Hook returned an unexpected error" message indicates that an error was thrown from within a React Hook (either built-in or custom) but wasn't caught by proper error handling mechanisms. React Hooks operate within the component lifecycle and render phases, and errors thrown from hooks can crash your component tree if not handled correctly. This error commonly appears when async operations inside hooks (like useEffect or custom hooks) fail, when promises reject without proper catch handlers, or when error boundaries don't properly catch hook-related errors. Unlike errors thrown during render which React can catch with error boundaries, async errors in hooks require explicit handling. Understanding this error is crucial because it affects how you structure error handling in functional components. Error boundaries, which are class components implementing componentDidCatch or getDerivedStateFromError, don't automatically catch errors from event handlers or async code within hooks.

Typical symptoms

  • Component crashes or unmounts unexpectedly when hook executes
  • Application shows blank screen or fallback UI after async operation
  • Console displays uncaught promise rejection errors
  • Error boundary catches error but doesn't provide specific hook context
  • Custom hook fails silently without proper error state management
  • Network requests or API calls in useEffect fail without user feedback
  • State updates after unmount warnings appear alongside errors

Common causes

  1. Async operations in useEffect or custom hooks throwing errors without try-catch blocks
  2. Promise rejections in hooks not handled with .catch() or try-catch in async functions
  3. Error boundaries not configured to catch async errors from hooks
  4. Custom hooks throwing errors synchronously without error state management
  5. Network request failures in hooks without proper error handling
  6. State updates attempted after component unmounts during async operations
  7. Missing error handlers when using libraries like react-query or SWR in custom hooks
  8. Errors thrown in event handlers called from hooks not wrapped in try-catch

How to fix "Hook returned an unexpected error"

1Add try-catch blocks to async hook operations

Wrap async code in useEffect or custom hooks with try-catch blocks to handle errors gracefully:

javascript
// Before - error not caught
useEffect(() => {
  const fetchData = async () => {
    const response = await fetch('/api/data');
    const data = await response.json();
    setData(data);
  };
  fetchData();
}, []);

// After - error properly caught
useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await fetch('/api/data');
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      setError(error.message);
      console.error('Failed to fetch data:', error);
    }
  };
  fetchData();
}, []);

For promise-based code, use .catch() method:

javascript
useEffect(() => {
  fetch('/api/data')
    .then(response => response.json())
    .then(data => setData(data))
    .catch(error => {
      setError(error.message);
      console.error('Fetch failed:', error);
    });
}, []);
2Implement error state management in custom hooks

Create custom hooks that return error state alongside data and loading states:

javascript
function useFetchData(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

// Usage in component
function MyComponent() {
  const { data, loading, error } = useFetchData('/api/data');

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{JSON.stringify(data)}</div>;
}
3Install and configure react-error-boundary for async errors

Use the react-error-boundary library which provides useErrorHandler hook for async errors:

bash
npm install react-error-boundary

Implement in your component:

javascript
import { ErrorBoundary, useErrorHandler } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function MyComponent() {
  const [data, setData] = useState(null);
  const handleError = useErrorHandler();

  useEffect(() => {
    fetch('/api/data')
      .then(response => response.json())
      .then(setData)
      .catch(handleError); // Propagates error to ErrorBoundary
  }, [handleError]);

  return <div>{data && JSON.stringify(data)}</div>;
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <MyComponent />
    </ErrorBoundary>
  );
}
4Prevent state updates after unmount

Use cleanup functions and abort signals to prevent errors from unmounted components:

javascript
useEffect(() => {
  let isMounted = true;
  const controller = new AbortController();

  const fetchData = async () => {
    try {
      const response = await fetch('/api/data', {
        signal: controller.signal
      });
      const data = await response.json();

      // Only update state if component is still mounted
      if (isMounted) {
        setData(data);
      }
    } catch (error) {
      // Ignore abort errors
      if (error.name !== 'AbortError' && isMounted) {
        setError(error.message);
      }
    }
  };

  fetchData();

  // Cleanup function
  return () => {
    isMounted = false;
    controller.abort();
  };
}, []);
5Add error boundaries at component tree levels

Wrap components using hooks with error boundaries to catch rendering errors:

javascript
import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo);
    // Log to error reporting service
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>Something went wrong</h2>
          <details>
            <summary>Error details</summary>
            <pre>{this.state.error?.toString()}</pre>
          </details>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <ErrorBoundary>
      <ComponentWithHooks />
    </ErrorBoundary>
  );
}
6Handle event handler errors separately

Event handlers in hooks need their own error handling since error boundaries don't catch them:

javascript
function MyComponent() {
  const [error, setError] = useState(null);

  const handleClick = async () => {
    try {
      const response = await fetch('/api/action', {
        method: 'POST',
        body: JSON.stringify({ action: 'submit' })
      });

      if (!response.ok) {
        throw new Error('Action failed');
      }

      const result = await response.json();
      // Handle success
    } catch (err) {
      setError(err.message);
      // Optionally propagate to error boundary
      // throw err;
    }
  };

  return (
    <div>
      <button onClick={handleClick}>Submit</button>
      {error && <div className="error">{error}</div>}
    </div>
  );
}
7Use React Query or SWR for better error handling

Consider using data fetching libraries that handle errors automatically:

javascript
// Using React Query
import { useQuery } from '@tanstack/react-query';

function MyComponent() {
  const { data, error, isLoading } = useQuery({
    queryKey: ['userData'],
    queryFn: async () => {
      const response = await fetch('/api/user');
      if (!response.ok) {
        throw new Error('Failed to fetch user');
      }
      return response.json();
    },
    retry: 3,
    onError: (error) => {
      console.error('Query error:', error);
    }
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{JSON.stringify(data)}</div>;
}

// Using SWR
import useSWR from 'swr';

const fetcher = async (url) => {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error('API request failed');
  }
  return response.json();
};

function MyComponent() {
  const { data, error } = useSWR('/api/data', fetcher, {
    onError: (err) => console.error('SWR error:', err)
  });

  if (error) return <div>Failed to load</div>;
  if (!data) return <div>Loading...</div>;
  return <div>{JSON.stringify(data)}</div>;
}

Advanced notes

Error Boundary Limitations: React error boundaries are class components that catch errors during render, lifecycle methods, and constructors of the whole tree below them. However, they do NOT catch errors in event handlers, asynchronous code (setTimeout, promises), server-side rendering, or errors thrown in the error boundary itself. This is why explicit try-catch blocks are essential for async hook operations.

Custom Error Handler Hook Pattern: Create a reusable useErrorHandler hook that combines state management with error boundary integration:

javascript
function useErrorHandler() {
  const [error, setError] = useState(null);

  useEffect(() => {
    if (error) {
      throw error; // Propagates to nearest error boundary
    }
  }, [error]);

  return setError;
}

Development vs Production: In development mode, React shows the error overlay which can mask proper error boundary behavior. Test error handling in production builds to verify actual user experience.

Error Logging Best Practices: Always log errors to a monitoring service (Sentry, LogRocket, etc.) in componentDidCatch or error handlers. Include component stack traces and user context for debugging. Consider implementing a global error handler for uncaught promise rejections:

javascript
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  // Log to monitoring service
});

TypeScript Typing: When using TypeScript, properly type your error states to distinguish between different error types and provide better type safety in error handlers.

Frequently asked questions

What does "Hook returned an unexpected error" mean?
The "Hook returned an unexpected error" message indicates that an error was thrown from within a React Hook (either built-in or custom) but wasn't caught by proper error handling mechanisms. React Hooks operate within the component lifecycle and render phases, and errors thrown from hooks can crash your component tree if not handled correctly. This error commonly appears when async operations inside hooks (like useEffect or custom hooks) fail, when promises reject without proper catch handlers, or when error boundaries don't properly catch hook-related errors.
How do I fix "Hook returned an unexpected error"?
This error occurs when a React Hook throws an error that isn't properly caught and handled, often due to async operations, improper error boundaries, or errors in custom hooks that propagate unexpectedly.
What causes "Hook returned an unexpected error"?
Async operations in useEffect or custom hooks throwing errors without try-catch blocks Promise rejections in hooks not handled with .catch() or try-catch in async functions Error boundaries not configured to catch async errors from hooks
Is the "Hook returned an unexpected error" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading