--- title: Lifecycle description: How resources render, mount, update, and unmount. --- ## Render and commit Like React, tap splits work into two phases: 1. **Render**: the resource function runs, hooks record their data, and a return value is produced. This phase has no side effects. 2. **Commit**: effects whose dependencies changed run. If an effect is re-running, its previous cleanup executes first. The resource is considered mounted after this phase. ```ts import { resource } from "@assistant-ui/tap"; import { useState, useEffect } from "react"; const useApp = () => { // --- render phase --- const [count, setCount] = useState(0); useEffect(() => { // --- commit phase --- console.log(`Mounted with count: ${count}`); return () => console.log("Cleaned up"); }, [count]); return { count }; }; const App = resource(useApp); ``` ### Effect ordering In React, effects run children-first, then parents (inside-out). This is because components can only render children by returning them, there's no way to run an effect after a child's effects. In tap, **effects run in the exact order they are called during the render pass**. Since `useResource` is just another hook, you can place `useEffect` calls before or after it: ```ts import { resource, useResource } from "@assistant-ui/tap"; import { useEffect } from "react"; const useParent = () => { useEffect(() => { console.log("1: before child"); }); const child = useResource(Child()); useEffect(() => { console.log("3: after child"); }); return child; }; const Parent = resource(useParent); const useChild = () => { useEffect(() => { console.log("2: child"); }); }; const Child = resource(useChild); // Mount order: 1, 2, 3 ``` This is intentional, it lets you run setup logic both before and after children, which is useful when a parent needs to react to data provided by a child. Cleanup on unmount runs in the same order as mount (FIFO), matching the order effects were originally registered. ## Mount and unmount A resource is **mounted** after its first commit. At this point, effects have run and the resource is live. A resource is **unmounted** when its owner removes it, all effect cleanups run and the resource is disposed. This happens automatically when: - A parent resource stops rendering the child via `useResource` - A React component using `useResource` unmounts - `root.unmount()` is called on a `createTapRoot` root ## Concurrent mode Tap has full interoperability with React's concurrent mode. Resources rendered via `useResource` work correctly with `startTransition`, `useDeferredValue`, ``, and other concurrent features, state updates are applied consistently without tearing. The `@assistant-ui/store` layer currently relies on `useSyncExternalStore` to bridge tap resources into React. This means store subscriptions always trigger synchronous re-renders, opting out of concurrent scheduling for those reads. This may change in a future release. ## Offscreen / Activity When a resource is hidden by ``, state updates that arrive while it's hidden are tracked and replayed once it becomes visible again. No configuration needed. ## Strict mode In development, resources render twice on mount to surface side effects in render, the same as React's ``. - **`useResource`** inherits the surrounding ``. - **`createTapRoot`** enables it automatically in development.