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 { 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

ModeDescription
"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

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

On this page