How to fix Suspense does not detect data loaded in useEffect in React

ReactADVANCEDMEDIUMUpdated Dec 10, 2025ยท 98 views

React Suspense does not detect data fetching that occurs inside useEffect hooks or event handlers. This is a fundamental design limitation because Suspense works by catching promises thrown during the render phase, not from side effects. To fix this, you must use Suspense-compatible data fetching patterns like render-as-you-fetch or libraries with built-in Suspense support.

Quick fix

Fixes Suspense does not detect data loaded in useEffect

// This will NEVER work with Suspense
function MyComponent() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData);
  }, []);
  
  if (!data) return <div>Loading...</div>; // Suspense won't catch this
  return <div>{data.title}</div>;
}
  1. Understand that useEffect is incompatible with Suspense by design
  2. Use a Suspense-compatible data fetching library
  3. Implement the render-as-you-fetch pattern manually
  4. Use Next.js Server Components for natural Suspense integration
react โ€” error
// This will NEVER work with Suspense
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []);
if (!data) return <div>Loading...</div>; // Suspense won't catch this
return <div>{data.title}</div>;
}
Suspense does not detect data loaded in useEffect

What this error means

React Suspense is a mechanism that allows components to "suspend" rendering while waiting for asynchronous operations like data fetching. However, Suspense only works when promises are thrown during the component render phase, not from side effects like useEffect. When you fetch data inside useEffect, the component renders first with initial state, then useEffect runs after rendering to start the data fetch. This "fetch-on-render" pattern is incompatible with Suspense because by the time useEffect runs, the render phase has already completed. Suspense expects components to throw promises during rendering so it can catch them and show fallback content while waiting. This is a fundamental architectural difference between traditional data fetching patterns and the Suspense model.

Typical symptoms

  • Suspense fallback never shows when data is fetched in useEffect
  • Component renders immediately with loading state instead of suspending
  • Suspense boundary does not catch or handle data loading from useEffect
  • No errors thrown but Suspense behaves as if no async operation is happening
  • Loading indicators appear inside the component rather than at the Suspense boundary
  • Nested components with useEffect fetching cause waterfall loading patterns
  • Data fetching happens after component mount rather than during render

Common causes

  1. Fetching data inside useEffect hooks which run after rendering completes, not during render phase
  2. Using the fetch-on-render pattern instead of render-as-you-fetch pattern
  3. Attempting to use traditional data fetching patterns with Suspense boundaries
  4. Not throwing promises during component rendering as Suspense expects
  5. Using data fetching libraries that do not have built-in Suspense support
  6. Misunderstanding that Suspense catches promises thrown during render, not from effects
  7. Trying to integrate Suspense with existing useEffect-based data fetching code without refactoring

How to fix "Suspense does not detect data loaded in useEffect"

1Understand that useEffect is incompatible with Suspense by design

Recognize that this is not a bug but a fundamental limitation. Suspense requires promises to be thrown during the render phase, while useEffect runs after rendering. You cannot make Suspense work with useEffect-based data fetching without completely changing your approach.

typescript
// This will NEVER work with Suspense
function MyComponent() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json())
      .then(setData);
  }, []);
  
  if (!data) return <div>Loading...</div>; // Suspense won't catch this
  return <div>{data.title}</div>;
}
2Use a Suspense-compatible data fetching library

The easiest solution is to use a library that has built-in Suspense support. Libraries like React Query, SWR, and Apollo Client have Suspense modes that handle promise throwing for you.

typescript
// React Query with Suspense
import { useSuspenseQuery } from '@tanstack/react-query';

function MyComponent() {
  // This works with Suspense - no loading state needed
  const { data } = useSuspenseQuery({
    queryKey: ['data'],
    queryFn: () => fetch('/api/data').then(res => res.json())
  });
  
  return <div>{data.title}</div>;
}

// Wrap with Suspense boundary
function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <MyComponent />
    </Suspense>
  );
}
3Implement the render-as-you-fetch pattern manually

If you cannot use a library, you can implement Suspense-compatible data fetching by creating a resource that throws promises during rendering. This requires wrapping your fetch call and managing promise states.

typescript
// Create a suspense-compatible resource
function wrapPromise<T>(promise: Promise<T>) {
  let status = 'pending';
  let result: T;
  let suspender = promise.then(
    (r) => {
      status = 'success';
      result = r;
    },
    (e) => {
      status = 'error';
      result = e;
    }
  );
  
  return {
    read() {
      if (status === 'pending') {
        throw suspender; // Suspense catches this
      } else if (status === 'error') {
        throw result;
      }
      return result;
    }
  };
}

// Start fetching BEFORE rendering
const resource = wrapPromise(fetch('/api/data').then(r => r.json()));

function MyComponent() {
  const data = resource.read(); // Throws promise if pending
  return <div>{data.title}</div>;
}
4Use Next.js Server Components for natural Suspense integration

If you are using Next.js with the App Router, Server Components can use async/await directly and work seamlessly with Suspense without any special setup.

typescript
// app/page.tsx - Server Component
async function MyComponent() {
  // This automatically works with Suspense
  const data = await fetch('https://api.example.com/data').then(r => r.json());
  return <div>{data.title}</div>;
}

export default function Page() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <MyComponent />
    </Suspense>
  );
}
5Migrate from useEffect to Suspense-compatible patterns gradually

If you have existing code using useEffect, refactor incrementally. Start by identifying components that fetch data on mount, then replace them one at a time with Suspense-compatible alternatives. Keep backward compatibility by maintaining both patterns during migration.

typescript
// Before: useEffect pattern
function OldComponent() {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetchData().then(setData);
  }, []);
  return data ? <Content data={data} /> : <Loading />;
}

// After: Suspense pattern
function NewComponent() {
  const data = useSuspenseQuery({ queryKey: ['data'], queryFn: fetchData });
  return <Content data={data} />;
}

// During migration, use feature flags or gradual rollout
function Component() {
  const useSuspense = useFeatureFlag('suspense-enabled');
  return useSuspense ? <NewComponent /> : <OldComponent />;
}
6Ensure Error Boundaries are in place

When using Suspense for data fetching, errors during fetch operations need to be caught by Error Boundaries. Always wrap Suspense components with an Error Boundary to handle failures gracefully.

typescript
import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    <ErrorBoundary fallback={<div>Something went wrong</div>}>
      <Suspense fallback={<div>Loading...</div>}>
        <MyComponent />
      </Suspense>
    </ErrorBoundary>
  );
}

Advanced notes

The Suspense model represents a paradigm shift from imperative data fetching (useEffect) to declarative data fetching (render-as-you-fetch). In the traditional model, you control when fetching happens through effects. With Suspense, components declare what data they need, and the fetching happens automatically during render. This enables better performance through request parallelization and eliminates waterfall problems where child components wait for parent data. The official React documentation notes that Suspense-enabled data fetching without an opinionated framework is still experimental, and the API for integrating custom data sources with Suspense remains unstable. For production use, it is strongly recommended to use established libraries (React Query, SWR, Relay) or frameworks (Next.js, Remix) that have stable Suspense integrations. When implementing custom Suspense resources, be aware of cache invalidation and refetching patterns - you need to manage when to create new resource instances versus reusing cached ones. The wrapPromise pattern shown above is simplified for demonstration; production implementations need proper error handling, cache management, and request deduplication.

Frequently asked questions

What does "Suspense does not detect data loaded in useEffect" mean?
React Suspense is a mechanism that allows components to "suspend" rendering while waiting for asynchronous operations like data fetching. However, Suspense only works when promises are thrown during the component render phase, not from side effects like useEffect. When you fetch data inside useEffect, the component renders first with initial state, then useEffect runs after rendering to start the data fetch.
How do I fix "Suspense does not detect data loaded in useEffect"?
React Suspense does not detect data fetching that occurs inside useEffect hooks or event handlers. This is a fundamental design limitation because Suspense works by catching promises thrown during the render phase, not from side effects. To fix this, you must use Suspense-compatible data fetching patterns like render-as-you-fetch or libraries with built-in Suspense support.
What causes "Suspense does not detect data loaded in useEffect"?
Fetching data inside useEffect hooks which run after rendering completes, not during render phase Using the fetch-on-render pattern instead of render-as-you-fetch pattern Attempting to use traditional data fetching patterns with Suspense boundaries
Is the "Suspense does not detect data loaded in useEffect" error serious?
It is a medium-severity error โ€” more of a blocker than a risk. The steps above resolve it.

Official resources & further reading