Skip to main content

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:

  1. Aborts the previous request's signal
  2. Discards any response that arrives from the aborted request
  3. 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?​

EventWhat gets cancelled
dependencies changePrevious request for that store
Component unmountsAll in-flight requests for that store
store.refresh() calledCurrent in-flight request (before re-fetch)
useRemoteUpdate: run() called againPrevious mutation
useRemoteUpdate: reset() calledCurrent 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] });