Cancellation
When dependencies change or a component unmounts, use-remote-data automatically cancels in-flight requests.
This prevents stale responses from overwriting fresh data and avoids wasted bandwidth.
How it works
Every fetch function receives an AbortSignal as its last argument.
Forward it to fetch() (or any API client that supports signals) to enable real cancellation:
const store = useRemoteData((signal) => fetch(`/api/users/${id}`, { signal }).then((r) => r.json()), {
dependencies: [id],
});
When id changes, the library:
- Aborts the previous request's signal
- Discards any response that arrives from the aborted request
- Starts a fresh request with a new signal
If you don't use the signal, the HTTP request still completes in the background — but the response is discarded. Forwarding the signal is optional but recommended: it saves bandwidth and frees server resources.
When does cancellation happen?
| Event | What gets cancelled |
|---|---|
dependencies change | Previous request for that store |
| Component unmounts | All in-flight requests for that store |
store.refresh() called | Current in-flight request (before re-fetch) |
useRemoteUpdate: run() called again | Previous mutation |
useRemoteUpdate: reset() called | Current mutation |
Works with any fetch library
The signal is a standard AbortSignal. It works with anything that supports it:
// fetch
useRemoteData((signal) => fetch(url, { signal }).then((r) => r.json()));
// axios
useRemoteData((signal) => axios.get(url, { signal }).then((r) => r.data));
Mutations too
useRemoteUpdate also passes a signal. If a user clicks "Save" twice rapidly,
the first request is aborted:
const saveStore = useRemoteUpdate((params, signal) =>
fetch('/api/save', {
method: 'POST',
body: JSON.stringify(params),
signal,
}).then((r) => r.json())
);
Try it — type fast
This example simulates a search API with an 800ms delay. Type quickly and watch the abort counter increase — only the final result renders.
import { useState } from 'react';
import { useRemoteData, Await } from 'use-remote-data';
let abortCount = 0;
// simulates a search API that respects AbortSignal
function search(query: string, signal: AbortSignal): Promise<string[]> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(
() =>
resolve([
`"${query}" — result 1`,
`"${query}" — result 2`,
`"${query}" — result 3`,
]),
800
);
signal.addEventListener('abort', () => {
clearTimeout(timeout);
abortCount++;
reject(new DOMException('Aborted', 'AbortError'));
});
});
}
export function Component() {
const [query, setQuery] = useState('react');
const store = useRemoteData((signal) => search(query, signal), {
dependencies: [query],
});
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
placeholder="Type to search..."
/>
<p style={{ fontSize: '0.85em', color: 'gray' }}>
Aborted requests: {abortCount}
</p>
<Await store={store}>
{(results, isStale) => (
<ul style={{ opacity: isStale ? 0.6 : 1 }}>
{results.map((r, i) => (
<li key={i}>{r}</li>
))}
</ul>
)}
</Await>
</div>
);
}
Backward compatible
If your fetch function doesn't accept a signal, everything still works. The library discards stale responses internally via request versioning — the signal just lets you cancel the actual HTTP request too.
// This still works — signal is ignored, stale responses are still discarded
useRemoteData(() => fetchUser(id), { dependencies: [id] });