All writing

Field note · Alexandru-Dan Pop

Correctly handling async/await in React components - Part 2

Correctly handling async/await in React components - Part 1 Context In those blog posts I...

  • #react
  • #javascript
  • #codequality
Published
Reading time
1 min read
react

Correctly handling async/await in React components - Part 1

The refactored

In the first post we cancelled requests and ignored stale results. Now let's move that logic into a custom hook. Our component only needs to display the joke. ⭐

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

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

const useJokeAsync = (more) => {
  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 joke;
};

export default function RandomJoke({ more, loadMore }) {
  const joke = useJokeAsync(more);

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

Aborting stops a supported request. The active flag separately prevents an old effect from updating state. We keep both inside the hook.

Conclusions

Custom hooks help isolate concerns like data fetching and keep components easy to read. No separate mounted-ref hook needed. ✅

© 2026 Alexandru-Dan PopSoftware · AI · Product