The handful of behaviors that aren't quite React.
Resources are React hooks, so almost everything carries over: the rules of hooks, dependency arrays, memoization, refs, and effect cleanup all work as you expect. This page covers the few places tap deliberately differs.
No setting state during render
Like React, tap splits work into a render phase (the resource function runs,
no side effects) and a commit phase (effects run). Unlike React, calling a
setter or dispatch during render throws.
To adjust state in response to props, derive it during render instead of setting it:
import { resource } from "@assistant-ui/tap";
import { useMemo } from "react";
const Filtered = resource(function Filtered(props: { items: string[]; query: string }) {
// derive, don't store-and-sync
const visible = useMemo(
() => props.items.filter((i) => i.includes(props.query)),
[props.items, props.query],
);
return { visible };
});Effects run in call order
In React, effects run children-first (inside-out), because a component can only
reach its children by returning them. In tap, useResource is just another hook,
so effects run in the exact order they are called, and you can place effects
before or after a child.
import { resource, useResource } from "@assistant-ui/tap";
import { useEffect } from "react";
const Parent = resource(function Parent() {
useEffect(() => console.log("1: before child"));
const child = useResource(Child());
useEffect(() => console.log("3: after child"));
return child;
});
const Child = resource(function Child() {
useEffect(() => console.log("2: child"));
});
// Mount order: 1, 2, 3Cleanup on unmount runs in the same order (FIFO). This lets a parent run setup both before and after its children, which is useful when it reacts to data a child provides.
The tree re-renders from the root
This is the biggest difference. In React, a state change re-renders only the component that changed and its descendants. In tap, the whole resource tree re-renders from the root: because a parent reads its child's return value directly, the parent must re-run to receive the new value, and so must its parent, all the way up.
useResourceRoot breaks this chain.
It creates a subtree boundary: everything below it re-renders independently, and
the parent does not re-render when the subtree updates. This is how you keep a
large tree efficient and how store libraries are built on tap.
useLayoutEffect collapses onto useEffect
tap has a single effect primitive. useLayoutEffect is accepted (so React code
ports cleanly) but behaves like useEffect inside a resource.
Strict mode
In development, resources hosted via useResource inherit React's strict mode: if
your component is inside <StrictMode>, the resource also renders twice on mount
to surface accidental side effects in render. Standalone roots created with
createResourceRoot enable this automatically in
development.