Handling Pagination in SolidJS Resources
The SolidJS createResource API does not provide a built-in cache for multiple keys. When the source signal (e.g., the page index) changes, the resource triggers a new fetch and replaces the previous value. To avoid refetching the same page when navigating back and forth, you must implement a caching layer outside the Resource API or use a dedicated state management library.
Implementing a Manual Page Cache
Because createResource is designed to track a single asynchronous value tied to a source, the most effective way to persist paginated data is to use a Map or a store as a cache layer. You can then use resource.mutate to instantly update the UI with cached data while the network request validates the content.
import { createSignal, createResource } from "solid-js";
const pageCache = new Map();
const fetchPage = async (page) => {
// Check cache first for immediate return
if (pageCache.has(page)) return pageCache.get(page);
const response = await fetch(`/api/data?page=${page}`);
const data = await response.json();
pageCache.set(page, data);
return data;
};
function Pagination() {
const [page, setPage] = createSignal(1);
const [data] = createResource(page, fetchPage);
return (
<div>
<button onClick={() => setPage(p => p - 1)}>Prev</button>
<button onClick={() => setPage(p => p + 1)}>Next</button>
<pre>{JSON.stringify(data())}</pre>
</div>
);
}
Cache Lifecycle and Invalidation
Since SolidJS does not prescribe an invalidation policy, the application owner must manage the cache lifecycle to prevent memory leaks. Recommended strategies include:
- Manual Reset: Clear the
Map when the user performs a search or filter action that renders previous pages obsolete.
- Time-to-Live (TTL): Store a timestamp alongside the data in the cache and check if
Date.now() - timestamp > TTL before returning the cached value.
- LRU Eviction: Use a Least Recently Used (LRU) cache if the number of pages is potentially unbounded, ensuring the browser memory does not grow indefinitely.
Ownership of the Cache
The cache should be owned by the state layer that matches the data's scope. If the pagination is global, a shared Store or Context is appropriate. If it is local to a specific view, the cache should be instantiated within the component or a custom hook to ensure it is garbage collected when the component unmounts.
Verification Steps
To verify your implementation is working as intended:
- Open the browser Network tab.
- Navigate from Page 1 to Page 2, then back to Page 1.
- Confirm that the request for Page 1 is either skipped (if returning from cache) or that the UI updates instantly via
mutate before the network request completes.
Diagnostic Note: Are you utilizing Server-Side Rendering (SSR)? If so, ensure your cache is not shared globally across requests on the server to avoid leaking data between different users.