mediumEngineering Practices#82

DRY — extract reusable hook

Prompt

Refactor duplicate code into a custom useFetch hook that takes a URL and returns { data, isLoading, error }.

Solution

function useFetch(url) {
  const [data, setData] = useState(null)
  const [isLoading, setIsLoading] = useState(true)
  const [error, setError] = useState(null)
  useEffect(() => {
    let cancelled = false
    setIsLoading(true)
    fetch(url)
      .then(r => r.json())
      .then(d => { if (!cancelled) setData(d) })
      .catch(e => { if (!cancelled) setError(e) })
      .finally(() => { if (!cancelled) setIsLoading(false) })
    return () => { cancelled = true }
  }, [url])
  return { data, isLoading, error }
}
Mentor's take

Custom hooks are how React does DRY for stateful logic: the fetch-loading-error triad appears on every screen, and extracting it means the pattern is fixed in one place when it needs fixing. Which it does — the naive version everyone writes first has a race condition: if url changes (or the component unmounts) while a request is in flight, the old response lands last and overwrites the new one. The cancelled flag in the effect cleanup is the minimal fix; it's also the answer to the "can't perform a state update on an unmounted component" class of bug. In an interview, volunteering the race is worth more than the extraction itself.

Two more signals in the shape: [url] in the dependency array makes the hook re-fetch when its input changes — a hook that ignores its argument after mount is a stale-data bug; and returning a named object { data, isLoading, error } beats a tuple because call sites stay self-documenting.

The honest scope statement: this hook has no caching, deduplication, or retry. That's not a flaw in the exercise — it's why production apps use TanStack Query or SWR, which are this hook with the hard parts done.

Red flag: DRY-ing two snippets that are only coincidentally similar. Duplication is cheaper than the wrong abstraction — extract when the pattern has proven itself (rule of three), not on the second occurrence.

Say it: "I extract the triad into useFetch with a cancellation flag for the in-flight race — and I say out loud that caching and dedup are why the real version is TanStack Query."