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
59 lines
2.1 KiB
Plaintext
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.
|