Implementing Offset‑Based Pagination in Apollo Client with InMemoryCache
Learn how to store each page of a list under a unique cache key and use fetchMore to append new results without reloading the whole dataset.
26 Oct 2025, 16:49 UTC

Useful answer
Apollo Client’s InMemoryCache can keep each page of an offset‑based list under a distinct cache key. When you call fetchMore with updated offset and limit variables, Apollo merges the newly returned items into the existing list, so the UI shows a growing collection without a full reload.
Mechanism – worked example
Below is a minimal React component that demonstrates the pattern. It assumes you have already installed @apollo/client and graphql packages.
import { gql, useQuery } from '@apollo/client';
const FEED_QUERY = gql`
query Feed($offset: Int!, $limit: Int!) {
feed(offset: $offset, limit: $limit) {
id
text
}
}
@connection(key: "feed", filter: ["offset", "limit"])
`;
function Feed() {
const { data, loading, error, fetchMore, refetch } = useQuery(FEED_QUERY, {
variables: { offset: 0, limit: 10 },
fetchPolicy: 'cache-and-network',
});
const loadMore = () => {
if (!data) return;
fetchMore({
variables: {
offset: data.feed.length, // next page starts after current items
limit: 10
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return {
feed: [...prev.feed, ...fetchMoreResult.feed]
};
}
});
};
if (loading) return Loading…
;
if (error) return Error: {error.message}
;
return (
{data.feed.map(item => (
- {item.text}
))}
Load more
);
}
export default Feed;
Where to run:
- Save the component in your React source tree (e.g.,
src/components/Feed.jsx). - Ensure an
ApolloClientinstance is wrapped around your app with anInMemoryCache(default setup). - Run the app with
npm startor your usual dev server.
Required permissions: none beyond normal frontend execution; the component only reads from the GraphQL endpoint you configured in ApolloClient.
Placeholders:
FEED_QUERY– replacefeedwith your actual connection field.offsetandlimit– adjust to match your backend’s pagination arguments.- The
@connectiondirective’skeymust be unique across queries; here we use"feed".
Expected checks:
- Open Apollo Client DevTools → Cache → ROOT_QUERY. You should see separate entries like
feed({"offset":0,"limit":10})andfeed({"offset":10,"limit":10}). - In the Network tab, each click of “Load more” triggers a single GraphQL request with the updated
offsetvariable; no full refetch occurs. - The rendered list grows by exactly the number of items returned per page, with no duplicates.
Risks:
- If the underlying data changes between requests (items inserted or deleted), offset‑based pagination can show duplicates or skip items.
- Using a non‑unique
@connectionkey causes different pages to share the same cache entry, leading to overwritten data. - Mutating the array returned by
fetchMore(e.g.,data.feed.push(...)) breaks Apollo’s immutable merge expectations and can corrupt the cache.
Limits
Offset‑based pagination works best when:
- The dataset is relatively small or changes infrequently.
- You can tolerate occasional duplicates or missed items in exchange for simpler implementation.
For highly dynamic data, consider keyset (cursor‑based) pagination or refetching the whole list after a mutation.
Common mistakes and how to avoid them
- Missing or incorrect
@connectiondirective. Without it,fetchMoretreats the new result as a full replacement, so the list resets to the latest page. Fix: Always include@connection(key: "uniqueKey", filter: ["offset", "limit"])and ensure thefilterarrays contain every variable that affects the query result. - Updating the offset variable incorrectly. Using a static offset (e.g., always
0) causes the same page to be refetched repeatedly, leading to an infinite loop or stale UI. Fix: Set the new offset topreviousResult.length(or the total count you already have). - Mutating the fetched data. Some developers push new items directly into the array returned by
fetchMore. This interferes with Apollo’s internal merge and can cause cache inconsistencies. Fix: Let Apollo handle the merge via theupdateQueryfunction (as shown) or rely on the default concatenation behavior when you returnObject.assign({}, prev, { feed: [...prev.feed, ...fetchMoreResult.feed] }).
Practical way to verify the result
- Run the component in a development environment.
- Open Apollo Client DevTools (install the browser extension if needed).
- Select the Cache tab, expand ROOT_QUERY, and verify that each page offset creates a distinct cache entry.
- Click “Load more” several times and confirm that the Network tab shows only one incremental request per click, with the correct
offsetvalue. - Check the rendered list: it should contain the concatenation of all fetched pages without repeated items.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.