Spoosh
Hooks

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/react

Setup

src/api/client.ts
import { Spoosh } from "@spoosh/core";
import { create } from "@spoosh/react";
import { cachePlugin } from "@spoosh/plugin-cache";
import { invalidationPlugin } from "@spoosh/plugin-invalidation";
import { deduplicationPlugin } from "@spoosh/plugin-deduplication";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
  cachePlugin({ staleTime: 5000 }),
  invalidationPlugin(),
  deduplicationPlugin({ read: "in-flight" }),
]);

export const { useRead, useWrite, usePages, useQueue } = create(spoosh);

Automatic Tag Generation

Spoosh automatically generates a cache tag from the resolved API path.

// api("users").GET()                              → tag: "users"
// api("users/:id").GET({ params: { id: 1 } })     → tag: "users/1"
// api("posts/:postId/comments").GET({ params: { postId: 5 } }) → tag: "posts/5/comments"

Custom Tags

Override the auto-generated tag with custom tags:

// Single custom tag
const { data } = useRead((api) => api("users").GET(), {
  tags: "my-users",
});

// Multiple custom tags
const { data } = useRead((api) => api("users").GET(), {
  tags: ["users", "dashboard"],
});

Cache Invalidation

The invalidationPlugin automatically refetches queries after mutations using wildcard pattern matching.

Pattern Matching

PatternMatches
"posts"Exact match only
"posts/*"All children (posts/1, posts/1/comments)
["posts", "posts/*"]posts AND all children (default)
const { trigger } = useWrite((api) => api("posts").POST());

// Default behavior — invalidates ["posts", "posts/*"]
await trigger({ body: { title: "New Post" } });

// Specific patterns
await trigger({
  body: { title: "New Post" },
  invalidate: ["posts", "posts/*"],
});

// Disable invalidation
await trigger({
  body: { title: "New Post" },
  invalidate: false,
});

Manual Invalidation

Target specific patterns to invalidate:

const { trigger } = useWrite((api) => api("posts").POST());

await trigger({
  body: { title: "New Post" },
  invalidate: ["posts", "users/*", "dashboard"],
});

Request Deduplication

The deduplicationPlugin prevents duplicate network requests when the same query is called multiple times simultaneously.

Deduplication Modes

ModeDescription
"in-flight"Reuse pending request if same query is in progress (default for reads)
falseDisable 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
  • usePages - Paginated data fetching
  • useQueue - Concurrent request queues with progress tracking
  • useSSE - Subscribe to real-time SSE streams

On this page