How would you write a simple useFetch custom hook?
Keep data, loading and error in state, fetch inside an effect keyed on the URL, and ignore or abort stale responses in the cleanup. Return the three values so components stay free of fetching details.
Explain it simply
The same fetch, loading flag and error handling appear in many screens. A useFetch hook hides this behind one line: const { data, loading, error } = useFetch(url).
The tricky part is race conditions. If the URL changes quickly, the first slow response may arrive after the second and overwrite fresh data with stale data. An AbortController in the cleanup cancels the old request, so only the latest one can set state.
For production apps most teams use TanStack Query or SWR, which add caching and retries, but writing this once shows you understand the problem.
Example
function useFetch(url) {
const [state, setState] = useState({ data: null, loading: true, error: null });
useEffect(() => {
const ctrl = new AbortController();
setState({ data: null, loading: true, error: null });
fetch(url, { signal: ctrl.signal })
.then(r => { if (!r.ok) throw new Error(r.status); return r.json(); })
.then(data => setState({ data, loading: false, error: null }))
.catch(err => {
if (err.name !== "AbortError") setState({ data: null, loading: false, error: err });
});
return () => ctrl.abort();
}, [url]);
return state;
}What to say in the interview
"The hook keeps data, loading and error in state, fetches in an effect depending on the URL, and aborts the request in cleanup to avoid race conditions and updates after unmount. In real projects I would reach for a library like TanStack Query for caching and retries."
Common mistakes
- Ignoring HTTP error status because fetch only rejects on network failure.
- Not handling the abort error as a normal case.