-
Notifications
You must be signed in to change notification settings - Fork 0
useResource
The useResource hook is a powerful data-fetching and caching primitive built on top of react-hook-lab's core primitives (useSharedState, useIndexedDB). It provides automatic background fetching, polling, persistence, tab-synchronization, and optimistic mutations.
Important
If you plan to use cache: "indexeddb" for offline persistence, you must register your IndexedDB stores globally at the root of your application (e.g. in App.tsx) before the hook is called:
import { createIndexedDB } from 'react-hook-lab';
createIndexedDB({
dbName: 'my-app-cache',
version: 1,
stores: ['users', 'statsStore']
});The bare minimum you need to use useResource is a unique key and an asynchronous fetcher function.
import { useResource } from 'react-hook-lab';
function SimpleProfile({ userId }) {
const { data, loading, error } = useResource({
key: `user:${userId}`, // A unique identifier for the resource cache
fetcher: async (signal) => {
// The function responsible for actually fetching the data.
// The `signal` can be passed to fetch() to automatically abort obsolete requests.
const res = await fetch(`/api/users/${userId}`, { signal });
return res.json();
}
});
if (loading) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return <div>{data.name}</div>;
}Most real-world applications will want to utilize background fetching, stale time, and optimistic updates.
import { useResource } from 'react-hook-lab';
function UserProfile({ userId }) {
const { data, loading, error, mutate, refresh } = useResource({
key: `user:${userId}`,
fetcher: (signal) => fetch(`/api/users/${userId}`, { signal }).then(res => res.json()),
// Cache Options
staleTime: 5 * 60 * 1000, // (Optional) Consider data fresh for 5 minutes. No background fetches will trigger during this window.
cache: "shared", // (Optional) "memory" (default), "shared" (syncs across browser tabs), or "indexeddb" (persists to disk).
});
if (loading && !data) return <div>Loading...</div>;
if (error) return <div>Error!</div>;
return (
<div>
<h1>{data.name}</h1>
<button onClick={() => {
// Update the cache instantly on the client side (optimistic update)
mutate(current => ({ ...current, name: "New Name" }));
}}>
Optimistic Update
</button>
<button onClick={refresh}>Force Refresh</button>
</div>
);
}For maximum control, useResource exposes extensive configuration for persistence, lifecycle events, and fine-grained rendering control.
import { useResource } from 'react-hook-lab';
function AdvancedDashboard() {
const resource = useResource({
key: "dashboard:stats",
fetcher: fetchDashboardStats,
// --- Core Options ---
enabled: true, // If false, pauses all automatic fetching.
initialData: null, // Sync initial data to skip the first loading state.
// --- Caching & Persistence ---
cache: "indexeddb", // Use the browser's IndexedDB for offline support.
namespace: "my-app", // Prefix the key internally to avoid collisions.
persist: { // IndexedDB specific options.
store: "statsStore", // Which IndexedDB store to save to (requires createIndexedDB setup).
exclude: ["secrets"], // Array of keys to strip out before saving to disk.
},
// --- Timing & Refetching ---
staleTime: 60000, // Time in ms before the data is considered stale.
gcTime: 300000, // Time in ms before unused cache is garbage collected.
refetchOnFocus: true, // Refetch when the user switches tabs back to the app.
refetchOnReconnect: true,// Refetch when the network connection comes back online.
// --- Error Handling ---
retry: 3, // Number of times to retry a failed fetch.
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000), // Exponential backoff.
keepPreviousData: true, // Keep showing old data while fetching a new key (useful for pagination).
// --- Lifecycle Callbacks ---
onSuccess: (data) => console.log("Fetched!", data),
onError: (error) => console.error("Failed!", error),
onMutate: (patch) => console.log("Optimistic update applied", patch),
onRefresh: () => console.log("Background refresh started"),
onInvalidate: () => console.log("Cache marked as stale"),
onRetry: (attempt, error) => console.warn(`Retry ${attempt} failed:`, error),
// --- Optimization ---
selector: (data) => data?.userStats, // Only subscribe to a specific slice of the data.
equalityFn: deepEqual, // Custom equality function to determine if the component should re-render.
});
return <div>{JSON.stringify(resource.data)}</div>;
}-
key: string(required): The unique cache key for the resource. -
fetcher: (signal: AbortSignal) => Promise<T>(optional): The asynchronous function that fetches data. -
cache: "memory" | "shared" | "indexeddb" | false: Caching strategy (default:"memory").-
"memory": Cache exists only for the lifetime of the component/hook. -
"shared": Cache is shared across all components using the same key. -
"indexeddb": Cache is persisted across sessions in the browser's IndexedDB.
-
-
persist: Options for IndexedDB caching.-
store: The IndexedDB object store to use. You MUST register this store globally viacreateIndexedDB. -
exclude: Keys of properties in the data object to omit from persistence.
-
-
staleTime: number: Time in ms until the resource is considered stale and needs background fetching (default:0). -
gcTime: number: Time in ms to keep the resource in cache after the last subscriber unmounts (default:5000). -
retry: number | boolean | ((failureCount: number, error: unknown) => boolean): Retry strategy on failure (default:0). -
enabled: boolean: Set to false to disable automatic fetching (default:true).
-
data: T | undefined: The fetched data. -
error: unknown | undefined: Any error encountered during fetching. -
status: "idle" | "loading" | "error" | "success" | "refreshing": The current state of the resource. -
loading: boolean: True if status is "loading" or "refreshing". -
updatedAt: number | null: Timestamp of the last successful fetch. -
refresh: () => Promise<void>: Manually trigger a background refresh. -
mutate: (updater: (data: T | undefined) => T) => void: Optimistically update the cached data. If called while a fetch is in-flight, the patch will be replayed over the incoming server data. -
select: <R>(selector: (data: T | undefined) => R, equalityFn?: (a: R, b: R) => boolean) => R: Optimize renders by selecting specific slices of data. -
invalidate: () => void: Mark the current data as stale and trigger an immediate fetch. -
reset: () => void: Clear the cache and reset the state.
When you call mutate, useResource immediately updates the UI. If a background fetch resolves after your mutation, useResource will intelligently replay your mutation patch on top of the newly fetched data, ensuring your local edits are not discarded by slightly stale server responses.
useResource is SSR-safe. On the server, it avoids triggering fetches, disables persistence adapters (like IndexedDB or BroadcastChannel), and falls back to "memory" caching logic to prevent memory leaks and server-side crashes.
Built with ❤️ by Saurav-TB-Pandey. | Report an Issue | NPM