reactjs
/

React Async Handling – Managing Promises & Side Effects

Last Sync: Today

On this page

5
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

reactjs

React Async Handling – Managing Promises & Side Effects

Asynchronous Patterns in React

Asynchronous operations (API calls, timers, or image loading) don't resolve instantly. In React, handling these requires careful state management to ensure the UI stays synchronized with the current status of the operation (Pending, Resolved, or Rejected).

The Race Condition Problem

A common bug occurs when a user triggers multiple async requests (e.g., clicking 'Next Page' rapidly). If the second request finishes before the first, the first request's older data might eventually overwrite the newer data when it finally resolves. This is known as a race condition.

React JSXRead-only
1
useEffect(() => {
  let active = true;

  const fetchData = async () => {
    const result = await api.getData(id);
    if (active) {
      setData(result);
    }
  };

  fetchData();
  return () => { active = false; }; // Cleanup prevents stale state updates
}, [id]);

Handling Cleanup with AbortController

The modern way to cancel an ongoing fetch request is using the AbortController. This actually stops the browser from continuing the network request, saving bandwidth and resources.

React JSXRead-only
1
useEffect(() => {
  const controller = new AbortController();

  fetch(url, { signal: controller.signal })
    .then(res => res.json())
    .then(data => setData(data))
    .catch(err => {
      if (err.name === 'AbortError') console.log('Fetch aborted');
      else console.error(err);
    });

  return () => controller.abort();
}, [url]);

The Future: React Suspense & the 'use' Hook

In 2026, React encourages using Suspense to handle loading states declaratively. Instead of manual isLoading booleans, you can wrap components in a Suspense boundary and use the use hook to read a promise directly in render.

React JSXRead-only
1
import { use, Suspense } from 'react';

function DataComponent({ promise }) {
  const data = use(promise); // Suspends until promise resolves
  return <div>{data.message}</div>;
}

// Usage
<Suspense fallback={<Spinner />}>
  <DataComponent promise={apiPromise} />
</Suspense>

Async Strategy Comparison

MethodComplexityBest For
useEffect + fetchModerateSimple, one-off side effects
Custom Async HooksHigherReusable logic across components
React Query / SWRLow (Once setup)Heavy data-driven applications
Suspense + 'use'Modern/LowDeclarative loading & Server Components

Try it yourself

import React, { useState, useEffect } from 'react';

function AsyncDemo() {
  const [id, setId] = useState(1);
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    let isMounted = true;
    setLoading(true);

    fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
      .then(res => res.json())
      .then(json => {
        if (isMounted) {
          setData(json);
          setLoading(false);
        }
      });

    return () => { isMounted = false; };
  }, [id]);

  return (
    <div style={{ padding: '20px' }}>
      <h3>Async Race Condition Protection</h3>
      <button onClick={() => setId(id + 1)}>Next Post ({id})</button>
      
      <div style={{ marginTop: '20px', opacity: loading ? 0.5 : 1 }}>
        {loading ? <p>Fetching...</p> : (
          <pre>{JSON.stringify(data, null, 2)}</pre>
        )}
      </div>
    </div>
  );
}

export default AsyncDemo;

Test Your Knowledge

Q1
of 2

What is the primary purpose of an AbortController in React?

A
To delete the component state
B
To cancel a network request to prevent side effects on unmounted components
C
To speed up the API response
D
To force a component to re-render
Q2
of 2

How does a race condition occur in React?

A
When two components render at the same exact millisecond
B
When an older async request resolves after a newer one and overwrites the state
C
When the browser runs out of memory
D
When using multiple useState hooks

Frequently Asked Questions

Why can't I make the useEffect callback async?

React expects the useEffect cleanup function (or nothing) to be returned. Async functions return a Promise, which would confuse React's cleanup mechanism.

How do I handle parallel async calls?

Use Promise.all([p1, p2]) or Promise.allSettled() to wait for multiple requests simultaneously without creating a waterfall delay.

Previous

react api calls

Next

react performance

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.