Infinite Scroll
Infinite scroll is a useRemoteDataMap where each page is a key and you grow the list of keys as the user scrolls.
const [cursors, setCursors] = useState([0]);
const pages = useRemoteDataMap<number, Page>((cursor, signal) => fetchPage(cursor, signal));
Each cursor maps to an independent store. Already-loaded pages stay rendered while the next page loads. If page 3 fails, pages 1 and 2 keep their data; the error and retry button only appear for page 3.
Try it
This snippet simulates 25 items across 5 pages, with random failures on roughly every 4th fetch. Click "Load more" to fetch the next page. When a page fails, click "Retry this page"; only the failed page re-fetches.
import { useState } from 'react';
import { Await, useRemoteDataMap } from 'use-remote-data';
// Simulated API — returns 5 items per page and a next cursor
interface Page {
items: string[];
nextCursor: number | null;
}
let fetchCount = 0;
const fetchPage = (cursor: number): Promise<Page> =>
new Promise((resolve, reject) => {
setTimeout(() => {
fetchCount++;
// Simulate a random failure on ~every 4th fetch
if (fetchCount % 4 === 0) {
reject(new Error('Network error'));
return;
}
const items = Array.from({ length: 5 }, (_, i) => {
const n = cursor + i + 1;
return `Item #${n}`;
});
const nextCursor = cursor + 5 < 25 ? cursor + 5 : null;
resolve({ items, nextCursor });
}, 600);
});
export function Component() {
const [cursors, setCursors] = useState([0]);
const pages = useRemoteDataMap<number, Page>((cursor) => fetchPage(cursor));
const loadMore = (nextCursor: number) =>
setCursors((prev) =>
prev.includes(nextCursor) ? prev : [...prev, nextCursor]
);
return (
<div>
{cursors.map((cursor) => (
<Await
key={cursor}
store={pages.get(cursor)}
loading={() => (
<p style={{ color: 'gray' }}>Loading page...</p>
)}
error={({ retry }) => (
<div style={{ color: 'red' }}>
<p>Failed to load page.</p>
<button onClick={retry}>Retry this page</button>
</div>
)}
>
{(page) => (
<>
{page.items.map((item) => (
<p key={item}>{item}</p>
))}
{page.nextCursor !== null && (
<button
onClick={() => loadMore(page.nextCursor!)}
>
Load more
</button>
)}
</>
)}
</Await>
))}
</div>
);
}
How it works
useState([0])tracks which cursors to render; starts with the first pageuseRemoteDataMapmanages one store per cursor; fetches are deduped by key- Each
<Await>independently handles loading, error, and success for its page - "Load more" appends the next cursor to the list, which triggers a new
<Await>to mount and start fetching - A failed page shows its own error + retry without affecting the rest
Compared to react-query
react-query has a dedicated useInfiniteQuery hook with built-in page management and cursor tracking. The useRemoteDataMap approach is simpler (no special hook, no getNextPageParam config), but you manage the cursor list yourself with useState.