Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

59 lines
2.1 KiB
Plaintext

---
title: How tap differs from React
description: 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.
## 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.
```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
```
Cleanup 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.
[`useTapRoot`](/tap/docs/tap/composition#usetaproot) 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.