Write a useAsync(fn) hook that returns { status, data, error, run } and ignores stale results
useAsync(fn) wraps any async function, like a search request, for a component. It returns { status, data, error, run }. status is "idle" until the first run, then "pending", then "success" or "error". data is the last successful result and error the last failure; both are null until there is one.
run(...args) calls fn(...args) right away, sets status to "pending" and error to null, and keeps data, so the page doesn't flash empty. When the promise fulfills, data is its value and status is "success". When it rejects, or fn throws, error is the reason, status is "error", and data stays as it was.
Only the latest call counts. When run is called again before an earlier call settles, the earlier call's result or failure is ignored, whichever order they settle in. run is the same function on every render and always calls the latest fn, so a component can pass an inline function and list run in an Effect's dependencies.
function TeaSearch() {
const [query, setQuery] = useState("");
const { status, data, error, run } = useAsync((text) => fetch("/api/teas?q=" + text).then((response) => response.json()));
return (
<>
<input
value={query}
onChange={(event) => {
setQuery(event.target.value);
run(event.target.value);
}}
/>
{status === "pending" && <p>Searching…</p>}
{error && <p role="alert">{error.message}</p>}
{data && <TeaList teas={data} />}
</>
);
}
import { useCallback, useLayoutEffect, useRef, useState } from "react";
export function useAsync(fn) {
// Return { status, data, error, run }.
return { status: "idle", data: null, error: null, run: () => {} };
}
- starts idle, with no data and no error
- goes pending, then success with the result
- a rejection sets the error and keeps the last data
- a function that throws right away is an error too
- an older call that settles last is ignored
- an older call that settles first doesn't end the wait
- an older call's failure is ignored
- run is the same function on every render and calls the latest fn
The answer
Write it yourself first. Then compare it with a reference solution and the checks it passes.
Checking your sign-in…