React Hooks
React hooks for data fetching with full type safety
The @spoosh/react package provides React hooks for data fetching with full type safety.
Installation
npm install @spoosh/core @spoosh/reactSetup
import { Spoosh } from "@spoosh/core";
import { createReactSpoosh } from "@spoosh/react";
import { cachePlugin } from "@spoosh/plugin-cache";
import { invalidationPlugin } from "@spoosh/plugin-invalidation";
import { deduplicationPlugin } from "@spoosh/plugin-deduplication";
const client = new Spoosh<ApiSchema, Error>("/api").use([
cachePlugin({ staleTime: 5000 }),
invalidationPlugin({ defaultMode: "all" }),
deduplicationPlugin({ read: "in-flight" }),
]);
export const { useRead, useWrite, useInfiniteRead } = createReactSpoosh(client);Automatic Tag Generation
Spoosh automatically generates cache tags from API paths. These tags enable precise cache invalidation.
// api("users").GET() → tags: ["users"]
// api("users/:id").GET({ params: { id: 1 } }) → tags: ["users", "users/1"]
// api("posts/:postId/comments").GET({ params: { postId: 5 } }) → tags: ["posts", "posts/5", "posts/5/comments"]Tags are hierarchical. Invalidating "users" will refetch all queries under /users/*, while invalidating "users/1" only affects that specific user.
Custom Tags
The unified tags option supports modes and custom tags:
// Mode only - generates auto tags based on mode
const { data } = useRead((api) => api("users").GET(), {
tags: "all", // Full hierarchy: ['users']
});
// Custom tags only - replaces auto-generated tags
const { data } = useRead((api) => api("users").GET(), {
tags: ["custom-users", "dashboard"],
});
// Mode + custom tags - combines both
const { data } = useRead((api) => api("users").GET(), {
tags: ["all", "dashboard-data"], // ['users', 'dashboard-data']
});Cache Invalidation
The invalidationPlugin automatically refetches stale queries after mutations.
Auto Invalidation Modes
| Mode | Description |
|---|---|
"all" | Invalidates all tags of the mutation path (default) |
"self" | Only invalidates the exact path |
"none" | No automatic invalidation |
const { trigger } = useWrite((api) => api("posts").POST);
await trigger({
body: { title: "New Post" },
invalidate: "all",
});Manual Invalidation
Target specific tags to invalidate:
const { trigger } = useWrite((api) => api("posts").POST);
await trigger({
body: { title: "New Post" },
invalidate: ["posts", "users/123/stats"],
});You can also use mode + tags:
await trigger({
body: { title: "New Post" },
invalidate: ["posts", "dashboard"], // invalidates all queries with these tags
});Request Deduplication
The deduplicationPlugin prevents duplicate network requests when the same query is called multiple times simultaneously.
Deduplication Modes
| Mode | Description |
|---|---|
"in-flight" | Reuse pending request if same query is in progress (default for reads) |
false | Disable deduplication |
const { data } = useRead((api) => api("users").GET(), { dedupe: "in-flight" });When multiple components call the same query simultaneously, only one network request is made:
function Header() {
const { data: user } = useRead((api) => api("me").GET());
return <div>{user?.name}</div>;
}
function Sidebar() {
const { data: user } = useRead((api) => api("me").GET());
return <nav>{user?.email}</nav>;
}Available Hooks
- useRead - Fetch data with automatic caching
- useWrite - Trigger mutations with loading states
- useInfiniteRead - Paginated data fetching