DEV Community

Cover image for Correctly handling async/await in React components
Alexandru-Dan Pop
Alexandru-Dan Pop

Posted on Edited on Originally published at alexandrudanpop.dev

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (11)

Collapse
 
zeeshan4242 profile image
Zeeshan •

Thanks Alex. I was struggling with memory-leak issues. It really helped!

Collapse
 
alexandrudanpop profile image
Alexandru-Dan Pop •

Glad to hear that!

Collapse
 
fefitin profile image
Fe •

Very interesting, I hadn’t come across those issues until I read your post. Quick question: why are you using a ref instead of a regular state var in your first fix? Thanks!

Collapse
 
alexandrudanpop profile image
Alexandru-Dan Pop •

By state var you mean having a setState({isUnmounted:true}) in the cleanup function of the first useEffect?

Don't think that will work, it might complain with the same: Cannot setState on unmounted warning. It seems refs are kept around even after Component unmounts, that's why they work in this case.

The React docs are a bit confusing, because they state refs live the same lifetime as components but obviously they stick around at least for as long as your async code still runs.

Collapse
 
fefitin profile image
Fe •

Great, that's good to know, thanks!

Collapse
 
monfernape profile image
Usman Khalil • • Edited

This is gold Alex. I've been on React for an year now and it's very helpful

Collapse
 
alexandrudanpop profile image
Alexandru-Dan Pop •

Thank you!

Collapse
 
performautodev profile image
performautodev •

You are awesome !

Collapse
 
mousticke profile image
Akim B. (mousticke.eth) •

Nice post. It's really helpful.
I'm just starting to learn the hooks system.
I know I can just google it but why would you use ref instead of state for componentIsMounted ? What is the purpose ?

Collapse
 
jamesthomson profile image
James Thomson •

Ref's don't cause the component to re-render so you can update the value without side effects - in this case, the side effect being an unwanted state update (due to the resolving async call) that occurs after the component has actually been in an unmounted.

Collapse
 
sandeep194920 profile image
Sandeep194920 •

Really like the content here, good work! May I ask which theme you're using for the code? I'm using vscode and tried searching for such a theme but couldn't find one. Can you please let me know?