Package stateful behavior into reusable, configurable values.
A Resource packages stateful behavior behind a JavaScript value. It renders with inputs, keeps Hook state between renders, mounts effects, and cleans up when it unmounts.
Its return value is its public API: the state and methods available wherever the Resource is rendered.
Package a Hook as a Resource
Start with an ordinary Hook, then pass it to resource():
import { resource } from "@assistant-ui/tap";
import { useState } from "react";
const useCounter = (props: { initialValue?: number }) => {
const [count, setCount] = useState(props.initialValue ?? 0);
return { count, increment: () => setCount((c) => c + 1) };
};
const Counter = resource(useCounter);useCounter contains the implementation. Counter is the reusable Resource.
Keep the implementation in a use-prefixed Hook so React's rules of Hooks lint
it normally.
Calling a Resource configures it:
const counter = Counter({ initialValue: 10 });This does not run useCounter or create state. It returns an inert
ResourceElement describing what to render, much like JSX describes a component
before React renders it.
Configuration is a value
Because a configured Resource is a value, an application can choose, pass, and nest it before any state is created:
import { useResource, type ResourceElement } from "@assistant-ui/tap";
type CounterState = ReturnType<typeof useCounter>;
function CounterButton({
counter = Counter({ initialValue: 0 }),
}: {
counter?: ResourceElement<CounterState>;
}) {
const state = useResource(counter);
return <button onClick={state.increment}>{state.count}</button>;
}The component owns where the state is rendered without owning its implementation or configuration. A caller can use the default or provide any other Resource that returns the same API.
Hosting a Resource
Use useResource to render a single Resource, or
useResources to render a keyed list
of Resources.
import { useResource } from "@assistant-ui/tap";
function CounterButton() {
const { count, increment } = useResource(Counter({ initialValue: 10 }));
return <button onClick={increment}>Count: {count}</button>;
}Rendering creates the Resource's state and returns its current public API. In a React component, the component re-renders when that state changes and the Resource unmounts with the component. Inside another Resource, it becomes a child in the same resource tree.
Arguments can change across renders without replacing the Resource's state. Effects respond through their dependency arrays, just as they do in a React component.
Next
- Composition: compose individual resources and keyed lists.
- Trees & Re-renders: give a resource tree its own scheduler with
useTapRoot. - Context: pass values through the resource tree without prop drilling.