Resources

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.