cf.completefrontend

Write the codeHard

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} />}
    </>
  );
}
Write this (useAsync.js)
import { useCallback, useLayoutEffect, useRef, useState } from "react";
 
export function useAsync(fn) {
  // Return { status, data, error, run }.
  return { status: "idle", data: null, error: null, run: () => {} };
}
The checks your code must pass
  • 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

Try it in the editor

The answer

Write it yourself first. Then compare it with a reference solution and the checks it passes.

Checking your sign-in…