Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

116 lines
2.8 KiB
Plaintext

---
title: Quickstart
description: Install Store and connect your first Tap resource to React.
---
Store (`@assistant-ui/store`) connects Tap resources to React with scoped, type-safe state management.
## Installation
```sh
npm install @assistant-ui/store @assistant-ui/tap
```
## Define your scope types
Register your scopes by augmenting the `ScopeRegistry` interface. This gives you type safety across all Store hooks.
```ts title="lib/store/counter-scope.ts"
import "@assistant-ui/store";
declare module "@assistant-ui/store" {
interface ScopeRegistry {
counter: {
methods: {
getState: () => { count: number };
increment: () => void;
};
};
}
}
```
## Create a resource
Define a Tap resource that returns `ClientOutput<"counter">`. This connects the resource's methods to the scope type you just registered.
```ts title="lib/store/counter-store.ts"
import { resource } from "@assistant-ui/tap";
import { useState, useMemo } from "react";
import type { ClientOutput } from "@assistant-ui/store";
const useCounterResource = ({
initialCount = 0,
}: {
initialCount?: number;
}): ClientOutput<"counter"> => {
const [count, setCount] = useState(initialCount);
const state = useMemo(() => ({ count }), [count]);
return {
getState: () => state,
increment: () => setCount((c) => c + 1),
};
};
export const CounterResource = resource(useCounterResource);
```
## Use it in React
Use `useAui` to create a store, `AuiProvider` to provide it to the tree, and `useAuiState` to subscribe to state.
```tsx title="app/CounterApp.tsx"
"use client";
import { useAui, AuiProvider } from "@assistant-ui/store";
import { CounterResource } from "@/lib/store/counter-store";
import { CounterDisplay } from "./CounterDisplay";
export const CounterApp = () => {
const aui = useAui({
counter: CounterResource({ initialCount: 0 }),
});
return (
<AuiProvider value={aui}>
<CounterDisplay />
</AuiProvider>
);
};
```
```tsx title="app/CounterDisplay.tsx"
"use client";
import { useAui, useAuiState } from "@assistant-ui/store";
export const CounterDisplay = () => {
const count = useAuiState((s) => s.counter.count);
const aui = useAui();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => aui.counter().increment()}>+</button>
</div>
);
};
```
`useAuiState((s) => s.counter.count)` subscribes to just the `count` value, so the component only re-renders when it changes.
`aui.counter()` returns the methods object, so `aui.counter().increment()` calls the `increment` method you defined in the resource.
## Next steps
<Cards>
<Card title="Scopes" href="/tap/docs/store/scopes">
Nest scopes with Derived and AuiProvider.
</Card>
<Card title="Events" href="/tap/docs/store/events">
Emit and subscribe to typed events.
</Card>
</Cards>