- Add optional requestKey parameter to UseFetchDataOptions - Implement module-level cache (inFlightRequests) to track in-flight requests - When requestKey is provided, multiple hook instances with same key share in-flight requests - Prevents duplicate API calls when multiple components fetch same data or rapid refresh calls - Cache entries are automatically cleared when response arrives (success or error) - Maintains backward compatibility: without requestKey, behaves as before - Adds comprehensive tests for deduplication scenarios This reduces bandwidth waste and prevents race conditions caused by concurrent requests for identical data. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
React Hook Fetch Cancellation
This folder follows a shared convention for network fetch cancellation in React hooks.
Patterns
1. Hooks with manual refresh
Hooks that expose a refresh() callback must use a long-lived AbortController stored in a ref:
- `const abortRef = useRef<AbortController | null>(null);
- Call
abortRef.current?.abort()before starting a new request. - Create a fresh controller before every
refresh()invocation. - Pass
controller.signalto the API function. - In the cleanup effect, abort the controller when the hook unmounts.
- After each
await, checksignal.abortedbefore updating state.
This prevents stale responses from overwriting newer results and avoids React state updates after unmount.
2. One-shot mount-only requests
Hooks that only fetch once inside useEffect and do not expose a manual refresh may use a local controller:
- Create
const controller = new AbortController();inside the effect. - Pass
controller.signalto the request. - Abort it in the effect cleanup.
- This is the simplest correct pattern for single-fetch hooks.
3. Do not use boolean cancelled flags for network requests
A boolean cancelled flag is not sufficient because it does not stop the underlying fetch. Abort signals are the correct cancellation mechanism for fetch-based hooks.
Polling with Drift Correction
The usePolledData hook implements drift-corrected polling to maintain accurate polling intervals despite variable fetch durations.
How it works
- Uses self-scheduling timeouts instead of fixed
setInterval - Tracks elapsed time from poll start to completion with
performance.now() - Calculates next delay as
Math.max(0, pollInterval - elapsed) - Schedules the next poll with drift compensation in the
onSuccesscallback - If a fetch takes longer than
pollInterval, the next poll starts immediately (delay = 0)
Why this matters
With fixed setInterval:
- If a fetch takes 2 seconds and
pollIntervalis 5 seconds - Actual polling interval becomes ~7 seconds (2s fetch + 5s interval)
- Effective polling rate drifts and wastes bandwidth
With drift correction:
- Total time from poll start to next poll start is always ~5 seconds
- Fetch duration doesn't affect the long-term polling rate
- Bandwidth and CPU usage remain consistent