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
152 lines
4.7 KiB
Plaintext
152 lines
4.7 KiB
Plaintext
---
|
|
title: Methods
|
|
description: Access scope methods with useAui.
|
|
---
|
|
|
|
Methods are the imperative API of a scope. They're the functions your resource returns: `increment`, `send`, `delete`, or anything else. You access them through `useAui()`.
|
|
|
|
## Defining methods
|
|
|
|
First, register the method signatures in `ScopeRegistry`:
|
|
|
|
```ts title="lib/store/counter-scope.ts"
|
|
import "@assistant-ui/store";
|
|
|
|
declare module "@assistant-ui/store" {
|
|
interface ScopeRegistry {
|
|
counter: {
|
|
methods: {
|
|
increment: () => void;
|
|
decrement: () => void;
|
|
reset: () => void;
|
|
};
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
Then create a resource that implements them. The return type `ClientOutput<"counter">` ties the resource to the scope: TypeScript will error if the returned methods don't match the registry:
|
|
|
|
```ts title="lib/store/counter-resource.ts"
|
|
import { resource } from "@assistant-ui/tap";
|
|
import { useState } from "react";
|
|
import type { ClientOutput } from "@assistant-ui/store";
|
|
|
|
const useCounterResource = (): ClientOutput<"counter"> => {
|
|
const [count, setCount] = useState(0);
|
|
|
|
return {
|
|
increment: () => setCount((c) => c + 1),
|
|
decrement: () => setCount((c) => c - 1),
|
|
reset: () => setCount(0),
|
|
};
|
|
};
|
|
|
|
const CounterResource = resource(useCounterResource);
|
|
```
|
|
|
|
Every function you return becomes a method on the scope. There's nothing special about them: they're plain functions that can call `useState` setters, trigger side effects, or do anything else.
|
|
|
|
## useAui
|
|
|
|
Call `useAui()` with no arguments inside any `AuiProvider` to get the current store:
|
|
|
|
```tsx
|
|
const aui = useAui();
|
|
```
|
|
|
|
The returned object has a property for every scope available in the current context. Crucially, `useAui()` does **not** re-render your component when scopes change: it returns a stable reference. The actual scope is only resolved when you call `aui.counter()`.
|
|
|
|
## Scope resolution
|
|
|
|
`aui.counter` is not the scope itself, it's an accessor. The scope resolves when you call it:
|
|
|
|
```tsx
|
|
// resolves the counter scope, returns its methods
|
|
aui.counter().increment();
|
|
```
|
|
|
|
This distinction matters. The `aui` object is stable across re-renders and scope changes. When a derived scope switches which item it points to, `aui` stays the same, but `aui.counter()` returns the new scope's methods. This is why you should always resolve at the point of use:
|
|
|
|
```tsx
|
|
const MessageActions = () => {
|
|
const aui = useAui();
|
|
|
|
return (
|
|
<button
|
|
onClick={() => {
|
|
// resolves at click time, always gets the current scope
|
|
aui.message().reload();
|
|
aui.thread().cancelRun();
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
```
|
|
|
|
### Don't resolve during render
|
|
|
|
Because `useAui()` doesn't subscribe to scope changes, resolving during render gives you a snapshot that can go stale. Use `useAuiState` to read state during render instead.
|
|
|
|
```tsx
|
|
const Counter = () => {
|
|
const aui = useAui();
|
|
|
|
// ❌ Don't resolve during render
|
|
const count = aui.counter().getState().count;
|
|
|
|
// ✅ Use useAuiState for render-time reads
|
|
const count = useAuiState((s) => s.counter.count);
|
|
|
|
// ✅ Resolve in event handlers, effects, or callbacks
|
|
const handleClick = () => aui.counter().increment();
|
|
};
|
|
```
|
|
|
|
For the same reason, avoid storing a resolved scope in a variable during render:
|
|
|
|
```tsx
|
|
// ❌ Resolves during render, can go stale
|
|
const counter = aui.counter();
|
|
const handleClick = () => counter.increment();
|
|
|
|
// ✅ Resolves at call time, always current
|
|
const handleClick = () => aui.counter().increment();
|
|
```
|
|
|
|
## Checking if a scope exists
|
|
|
|
Calling `aui.counter()` throws if the `counter` scope hasn't been provided by any `AuiProvider` above. To safely check, inspect the accessor's `source` property:
|
|
|
|
```tsx
|
|
const aui = useAui();
|
|
|
|
if (aui.counter.source !== null) {
|
|
// safe to call
|
|
aui.counter().increment();
|
|
}
|
|
```
|
|
|
|
`source` is `null` when the scope isn't available. Any other value (`"root"`, a parent scope name) means it's safe to resolve.
|
|
|
|
## Subscribing to scope identity
|
|
|
|
<Callout type="info">
|
|
This is an advanced pattern. In the entire assistant-ui codebase, there are only two use cases for this.
|
|
</Callout>
|
|
|
|
Sometimes you need to know when the scope itself changes, for example to register/unregister with an external system when a derived scope switches to a different item.
|
|
|
|
Since `useAui()` doesn't re-render on scope changes, you need to opt in explicitly. Use `useAuiState` to subscribe to the scope identity:
|
|
|
|
```tsx
|
|
const thread = useAuiState(() => aui.thread());
|
|
|
|
useEffect(() => {
|
|
analytics.register(thread);
|
|
return () => analytics.unregister(thread);
|
|
}, [thread]);
|
|
```
|
|
|
|
`aui.thread()` returns a stable methods object per scope instance. When a derived scope switches which thread it points to, `useAuiState` detects the new reference and re-renders, triggering the effect cleanup and re-registration.
|