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

66 lines
2.6 KiB
Plaintext

---
title: Motivation
description: Why tap exists and when to reach for it.
---
## React hooks are great, until your data doesn't match your UI
React hooks let you colocate state and effects with the component that uses them.
This works beautifully when the shape of your data matches the shape of your UI. A
`<Counter>` component owns a `count` state. A `<Form>` component owns its input
states. Simple.
But over the lifetime of any non-trivial app, data needs to be accessed from
multiple places in the UI. So you hoist state up. Then up again. Eventually, most
of your state lives in a store at the root of your app: **global state**.
## You can't use hooks for global state
Once state is global, hooks can't manage it. You can't call `useState` in a loop
for each message in a thread, that would break the rules of hooks. The very
paradigm that made React so pleasant doesn't apply to your most important state.
This is why state management libraries are so popular. Zustand, Jotai, Redux, and
MobX all facilitate this migration into global stores and let you efficiently
access that state from anywhere in your app.
## Hooks, without the UI
tap brings `useState`, `useEffect`, `useMemo`, and the rest outside of React
components and into standalone **resources**. Same rules of hooks, same mental
model, just not tied to the UI tree. A resource owns state the way a component
does, but returns a plain value instead of JSX, so you can use it for the global
state hooks normally can't reach.
## Lifecycle
Unlike Zustand, Jotai, and RxJS, tap has a concept of **lifecycle**: render and
commit phases, mount and unmount, and `useEffect`. That lets resources handle
server-side rendering, Suspense, hydration, mounting, and cleanup the same way a
React component does.
## Composable configuration
tap shapes assistant-ui's API so that every piece of configuration is a
composable resource:
```ts
const aui = useAui({
cloud: AssistantCloud({ apiKey: "..." }),
threads: CloudThreadList({
thread: () => AISDKThread({ transport: /* ... */ }),
}),
});
```
Each of these — `AssistantCloud(...)`, `CloudThreadList(...)`, `AISDKThread(...)` — returns a [`ResourceElement`](/tap/docs/tap/resources#resourceelements): a simple `{ hook, args }` object that carries both the implementation and its configuration. Users can swap, compose, and nest them freely.
## Architecture
tap ships as two packages:
- **`@assistant-ui/tap`**: the hooks engine, general-purpose and not tied to
assistant-ui.
- **`@assistant-ui/store`**: the glue between tap and React, connecting resources
to components via `useSyncExternalStore` and React context.