Documents
resources
resources
Type
External
Status
Published
Created
Mar 17, 2026
Updated
Mar 17, 2026

A resource is a self-contained unit of reactive state and logic — like a React component, but without UI.

Defining a resource#

You define a resource with the resource function.

import { resource, tapState } from "@assistant-ui/tap";

const Counter = resource(({ initialValue = 0 }: { initialValue?: number }) => {
  const [count, setCount] = tapState(initialValue);

  return {
    count,
    increment: () => setCount((c) => c + 1),
  };
});

resource() returns a factory function. Calling the factory creates a ResourceElement — a lightweight description of what to render, not an active instance yet.

ResourceElements#

A ResourceElement is a simple { type, props } object — the same idea as a React JSX element ({ type, props } under the hood), but without JSX syntax.

const element = Counter({ initialValue: 10 });
// { type: Counter, props: { initialValue: 10 } }

We deliberately avoided JSX for resource elements. In our testing, JSX confused users because it looked like UI code but wasn't rendering anything visible. Instead, we use a calling convention inspired by Flutter — ResourceName({ props }) — so that it reads like normal function calls.

Just like in React, a ResourceElement is inert. It doesn't do anything on its own — it's a description of what to render, not an active instance. See Instances for how to bring them to life.

Props#

Resources can accept props, just like React components. In the example above, Counter takes an initialValue prop.

Props are passed to a resource instance by its owner — which can be another resource (via tapResource), a React component (via useResource), or imperative code (via createResourceRoot).

When a resource re-renders with new props, hooks like tapEffect and tapMemo can react to the changes through their dependency arrays.

Return value#

Resources can return a value. This is how you expose state and methods to the outside world. Unlike React components which return JSX nodes, resources can return any JavaScript value — objects, arrays, numbers, strings, or anything else.

The return value is what you get when you read from an instance — directly from useResource in React, or via handle.getValue() with createResourceRoot.

Keys#

You can attach a stable key to a ResourceElement with withKey. Keys are used to preserve identity when rendering lists with tapResources.

import { withKey } from "@assistant-ui/tap";

const element = withKey("my-counter", Counter({ initialValue: 10 }));
// { type: Counter, props: { initialValue: 10 }, key: "my-counter" }

Keys work the same way as React's key prop — when the key stays the same, the resource keeps its state. When the key changes, the resource is unmounted and a fresh one is created.

Instances#

A ResourceElement is just a description — it doesn't do anything on its own. To bring it to life, you create an instance. There are two ways to do this:

useResource#

Use useResource inside React components. The resource's lifecycle is tied to the component — it mounts when the component mounts and unmounts when the component unmounts.

import { useResource } from "@assistant-ui/tap/react";

function CounterComponent() {
  const { count, increment } = useResource(Counter({ initialValue: 10 }));

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

The component re-renders whenever the resource's state changes.

createResourceRoot#

Use createResourceRoot for imperative, framework-agnostic usage.

import { createResourceRoot } from "@assistant-ui/tap";

const root = createResourceRoot();
const handle = root.render(Counter({ initialValue: 10 }));

// read state
handle.getValue().count; // 10

// subscribe to changes
handle.subscribe(() => {
  console.log(handle.getValue().count);
});

// call methods
handle.getValue().increment();

// update props
root.render(Counter({ initialValue: 20 }));

// cleanup
root.unmount();