All writing

Field note · Alexandru-Dan Pop

Correctly handling async/await in React components

Learn to correctly use async/await in React components

Published
Reading time
1 min read
Correctly handling async/await in React components

Why is it so complex?

While we wait for an async request, the component props can change or the component can unmount. An older request can also finish last and overwrite a newer result. 🐛

How to fix

Cancel the request during cleanup and ignore results from the old effect. With Axios, use AbortController.

This example expects your own /api/joke endpoint to return { "joke": "..." }.

import React, { useState, useEffect } from "react";
import axios from "axios";

export default function RandomJoke({ more, loadMore }) {
  const [joke, setJoke] = useState("");

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

    async function fetchJoke() {
      try {
        const response = await axios("/api/joke", {
          signal: controller.signal,
        });
        if (active) setJoke(response.data.joke);
      } catch (err) {
        if (active && !axios.isCancel(err)) console.error(err);
      }
    }

    fetchJoke();
    return () => {
      active = false;
      controller.abort();
    };
  }, [more]);

  return (
    <div>
      <h1>Here's a random joke for you</h1>
      <h2>{joke}</h2>
      <button onClick={loadMore}>More...</button>
    </div>
  );
}

Each effect run gets its own controller and active flag. Cleanup aborts the request and prevents stale results from updating state. This also handles React Strict Mode's extra setup/cleanup cycle. ✅

Conclusions

Keep cleanup close to the async work. Next, let's move it into a reusable hook.

Correctly handling async/await in React components - Part 2

© 2026 Alexandru-Dan PopSoftware · AI · Product