Custom Hooks — Advanced React challenge.
Build a useFetch hook with a shared, module-level in-memory cache. Repeated requests for the same URL — whether from a remount or from a second component mounted at the same moment — must reuse cached data or an in-flight request instead of firing a duplicate fetch. The hook must also be resilient to callers passing a new fetcher function reference on every render.
Requirements
- A cache lives at module scope (outside the hook function) so it's shared across every component and survives unmounts.
- The first request for a URL calls fetcher(url) and stores the resulting promise in the cache immediately, before it resolves.
- A second request for the SAME URL while the first is still pending must NOT call fetcher again — it reuses the existing promise.
- Once resolved, later requests for that URL return the cached data instantly, with loading: false, and never call fetcher again.
- A different URL always triggers its own fresh fetcher call.
- A rejected fetch sets { data: null, loading: false, error } correctly.
- The effect that triggers fetching must depend on `url` only — NOT on the `fetcher` reference. Callers are not required to memoize fetcher; re-renders that pass a structurally-new fetcher function must not re-trigger a fetch (this is what caused a browser-crashing infinite loop in earlier drafts).
- Export a pure helper `getStatusClassName({ loading, error })` that returns "status-loading" when loading is true, "status-error" when error is truthy, otherwise "status-success" — used by consumers to drive CSS styling for each state.
Signature
function useFetch(url: string, fetcher: (url: string) => Promise<any>): {
data: any;
loading: boolean;
error: Error | null;
}
function resetFetchCache(): void
function getStatusClassName(state: { loading: boolean; error: Error | null }): stringExample
const { data, loading, error } = useFetch("/api/user/1", fetchUser);
// two components calling useFetch("/api/user/1", fetchUser) at the same
// time only ever trigger ONE real call to fetchUser
// re-rendering with a brand-new fetcher reference for the same url
// must NOT trigger another fetchConstraints
- Do not use component-local state (e.g. useState/useRef inside the hook alone) as the cache — it must be module-level and shared.
- Cache the in-flight PROMISE, not just the eventual result — that's what makes concurrent-request deduplication possible.
- The effect's dependency array must NOT include `fetcher` — only `url`. Read fetcher via a ref or call it directly inside the effect without listing it as a dependency.