--- title: Composition description: Nest and compose resources with useResource, useResources, and useTapRoot. --- Resources can render other resources. Child resources get their own fiber, lifecycle, and state, just like React components rendering other components. **Key difference from React:** parent resources can directly access the return values of their children. In React, a parent component never sees what its children render. In tap, `useResource` returns the child's value directly, making composition a tool for building up state and logic, not just trees. ## useResource Render a single child resource. The child has its own state and effects, and is automatically cleaned up when the parent unmounts. ```ts import { resource, useResource } from "@assistant-ui/tap"; import { useEffect } from "react"; const useTimer = () => { const counter = useResource(Counter()); useEffect(() => { const interval = setInterval(() => { counter.increment(); }, 1000); return () => clearInterval(interval); }, []); return { count: counter.count }; }; const Timer = resource(useTimer); ``` ### Controlling re-renders The child re-renders when the element identity changes. The [React Compiler](https://react.dev/learn/react-compiler) keeps the element stable across renders when its props are unchanged, so the child only re-renders when its inputs actually change, no manual dependency tracking required. ```ts const value = useResource(Counter({ incrementBy })); ``` ## useResources This API is experimental and may change. Render a dynamic list of child resources, passed as an array. Each element **must** have a key via `withKey`. Resources are preserved across renders when their key stays the same. ```ts import { resource, withKey, useResources } from "@assistant-ui/tap"; import { useState } from "react"; const useTodoList = () => { const [items, setItems] = useState([ { id: "1", text: "Learn tap" }, { id: "2", text: "Build something" }, ]); const todos = useResources( items.map((item) => withKey(item.id, TodoItem({ text: item.text }))), ); return { todos, add: (text: string) => setItems((prev) => [...prev, { id: crypto.randomUUID(), text }]), }; }; const TodoList = resource(useTodoList); ``` Keys must be unique; a missing or duplicate key throws. ### Key behavior - **Same key, same type**: the existing fiber is reused and re-rendered with new props - **Same key, different type**: the old fiber is unmounted and a new one is created - **Removed key**: the fiber is unmounted and cleaned up This is the same model as React's `key` prop on list elements. ## useTapRoot `useTapRoot` takes a render callback and returns a stable `{ getValue, subscribe }` handle instead of the child's value directly. The parent doesn't re-render when the child updates, consumers subscribe to changes instead. See [Trees & Re-renders](/tap/docs/tap/trees-and-rerenders) for why this matters. Pass a **named function** (`function CounterRoot() { ... }`), not an arrow. The body calls hooks, so the name is what lets React's rules-of-hooks lint it. ```ts import { useResource, useTapRoot } from "@assistant-ui/tap"; const counter = useTapRoot(function CounterRoot() { return useResource(Counter()); }); // read current value counter.getValue(); // { count: 0, increment: ... } // subscribe to changes const unsub = counter.subscribe(() => { console.log(counter.getValue().count); }); ``` The returned object has a stable identity and won't change across renders. This is the pattern used to build store libraries on top of tap. ## When to use which | Hook | Use when | | --- | --- | | `useResource` | You need an independent child with its own state and lifecycle | | `useResources` | You have a dynamic list of children | | `useTapRoot` | You need to expose a child as a subscribable store |