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. ✅
Top comments (2)
Well done, ive been struggling how to appropriately handle async storage because the Ajax request will already send the nulled token before the DB is resolved, but this saved my day, thanks 💪
Glad I could be of help, mate!