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] });