API reference · generated from source
Class: Retree#
Defined in: Retree.ts:173
Main entry point for use with Retree package. Exposes utility functions for observing to changes to an object and its children.
Constructors#
Constructor#
new Retree(): Retree;Returns#
Retree
Methods#
applyChanges()#
static applyChanges(changes): void;Defined in: Retree.ts:783
Replay a change batch forward, re-applying the state the records describe mutating to.
Parameters#
| Parameter | Type | Description |
|---|---|---|
changes | INodeFieldChanges<unknown>[] | Change records exactly as emitted to a Retree.on listener (one batch). |
Returns#
void
Remarks#
The redo counterpart of Retree.applyInverse: records apply in
emission order and each record's new state is applied at
record.key on the managed node behind record.node. Structural
records replay structurally (array insert/remove records splice,
delete-marked records delete, clear summary records clear), so
replaying a batch that Retree.applyInverse undid restores the
exact post-mutation state.
The whole batch applies inside one Retree.runTransaction. Throws when a record's node is no longer Retree-managed.
Example#
project.tasks.push({ title: "Tests" });
Retree.applyInverse(lastChanges); // undo
Retree.applyChanges(lastChanges); // ✅ redo: "Tests" is backapplyInverse()#
static applyInverse(changes): void;Defined in: Retree.ts:751
Apply the inverse of a change batch, restoring the state the records describe mutating away from.
Parameters#
| Parameter | Type | Description |
|---|---|---|
changes | INodeFieldChanges<unknown>[] | Change records exactly as emitted to a Retree.on listener (one batch). |
Returns#
void
Remarks#
Records are applied in reverse order; for each one, previous is
restored at record.key on the managed node behind record.node
(see Retree.managed). Structural records restore structure
exactly: array insert/remove records splice elements back into
place, add-marked records delete the key they created, delete-
marked records restore the deleted entry, and Map.clear/Set.clear
restore every discarded entry from their per-entry records (see
TNodeFieldChangeOp).
The whole batch applies inside one Retree.runTransaction, and every write goes through managed nodes, so listeners (and React) observe the restoration as one normal batched change.
Known inexact inverses, by design: a direct array.length = n
assignment that shrank the array restores only the length (the
discarded elements emitted no records), and a plain write that
implicitly extended an array restores the value but not the shorter
length.
Throws when a record's node is no longer Retree-managed — records must be applied to the live tree that emitted them.
Example#
const project = Retree.root({ tasks: [{ title: "Docs" }] });
let lastChanges: INodeFieldChanges[] = [];
Retree.on(project, "treeChanged", (_node, changes) => {
lastChanges = changes;
});
project.tasks.push({ title: "Tests" });
Retree.applyInverse(lastChanges); // ✅ tasks is back to one entryclearListeners()#
static clearListeners(node, shallow?): void;Defined in: Retree.ts:1222
Clear all listeners for a given node.
Parameters#
| Parameter | Type | Default value | Description |
|---|---|---|---|
node | object | undefined | node to clear all listeners for |
shallow | boolean | true | when false, also unsubscribes every descendant node — the full subtree, not just direct children. |
Returns#
void
Remarks#
Equivalent to calling each unsubscribe function returned by Retree.on.
Prefer storing and calling the unsubscribe returned from
Retree.on when you own a single subscription. Use
clearListeners when you own every listener for a node, such as during
teardown of a Retree-managed integration.
Example#
const root = Retree.root({ child: { grandchild: { count: 0 } } });
Retree.on(root, "nodeChanged", () => {});
Retree.on(root.child, "nodeChanged", () => {});
Retree.on(root.child.grandchild, "nodeChanged", () => {});
Retree.clearListeners(root, false); // clears all threeclone()#
static clone<TNode>(node): TNode;Defined in: Retree.ts:328
Clone a Retree-managed node into a detached object that can be assigned somewhere else as a new structural child.
Type Parameters#
| Type Parameter |
|---|
TNode extends object |
Parameters#
| Parameter | Type | Description |
|---|---|---|
node | TNode | Existing Retree-managed node to copy. |
Returns#
TNode
A detached copy of the node's current raw data.
Remarks#
Use this when two places need independent state initialized from the same current data. The clone is detached until you assign it into a Retree tree. Mutating the clone after assignment emits for the clone's new structural location, not the source node.
Do not use clone when the original object should simply move; use
Retree.move. Do not use clone for a selected-item pointer; use
Retree.link or @link.
Example#
const project = Retree.root({ tasks: [{ title: "Draft" }] });
const copy = Retree.clone(project.tasks[0]);
project.tasks.push(copy); // ✅ copy becomes a new child
project.tasks[1].title = "Published"; // source task is unchangedeffect()#
static effect(fn, options?): () => void;Defined in: Retree.ts:690
Run an auto-tracked reaction: fn runs immediately under dependency
tracking and re-runs whenever a tracked dependency changes.
Parameters#
| Parameter | Type | Description |
|---|---|---|
fn | () => void | Reaction body; run once immediately, then on every relevant dependency change. |
options | RetreeEffectOptions | Optional RetreeEffectOptions. |
Returns#
Unsubscribe function that stops all re-runs.
() => void
Remarks#
effect is the third subscription primitive next to Retree.on
(one node, every change) and Retree.select (derived value,
change-compared). It has no selected value and no comparison: every
dependency change that survives validation re-runs fn. Reads are
trapped exactly like the selector-only Retree.select(() => ...) form
— property reads subscribe to their owner node and validate by value,
so unrelated writes to a tracked node skip the re-run. Wrap reads that
should not subscribe in Retree.untracked.
fn may write Retree state. A run that changes the value of a
property it already read re-runs after the current run completes —
including the creation run, so a self-converging effect reaches its
fixed point before Retree.effect returns. A cascade of more than 100
synchronous re-runs throws an error naming the effect, because the
effect cannot converge. Note that reads of a property the same run
also writes are excluded from dependency tracking, exactly as in
tracked selectors.
Errors thrown by fn do not kill the reaction: they are passed to
options.onError when provided, and rethrown asynchronously (as an
uncaught exception) otherwise. See RetreeEffectOptions.onError.
Example#
const settings = Retree.root({ theme: "dark", fontSize: 14 });
const stop = Retree.effect(() => {
document.body.dataset.theme = settings.theme;
});
settings.theme = "light"; // ✅ effect re-runs
settings.fontSize = 16; // ❌ not read by the effect; skipped
stop();isNode()#
static isNode(value): value is object;Defined in: Retree.ts:894
Check whether a value is a Retree-managed node.
Parameters#
| Parameter | Type | Description |
|---|---|---|
value | unknown | Value to check; any type is accepted. |
Returns#
value is object
true when the value is a Retree-managed node.
Remarks#
Returns true only for values wrapped by a Retree proxy — objects
returned by Retree.root or read through an existing Retree
tree. Raw values are not managed nodes: this returns false for the
objects behind Retree.raw, for previous/new change payload
values, and for plain objects that were never rooted.
Use this as the guard in front of APIs that require a managed node
when a value may come from either side of the proxy boundary, such as
data that is sometimes Retree state and sometimes plain wire data:
Retree.isNode(value) ? Retree.raw(value) : value.
Example#
const project = Retree.root({ items: [{ score: 1 }] });
Retree.isNode(project); // true
Retree.isNode(project.items[0]); // true
Retree.isNode(Retree.raw(project)); // false — raw values are not managed
Retree.isNode({ score: 1 }); // false — never rootedlink()#
static link<TNode>(node): RetreeLink<TNode>;Defined in: Retree.ts:297
Create a reactive pointer to an existing Retree-managed node.
Type Parameters#
| Type Parameter |
|---|
TNode extends object |
Parameters#
| Parameter | Type | Description |
|---|---|---|
node | TNode | Existing Retree-managed node to point at. |
Returns#
RetreeLink<TNode>
A Retree-managed link object whose current points at node.
Remarks#
The returned link can be stored in a Retree tree without reparenting the
linked target. Replacing link.current emits for the link; mutating the
linked target emits from its structural location.
Use this for selected items, cross-references, and pointers into another part of the same tree. Do not use a link when ownership should move; use Retree.move instead. Do not use a link when the two locations should diverge independently; use Retree.clone instead.
Example#
const root = Retree.root({
tasks: [{ title: "Docs" }],
selected: null as RetreeLink<{ title: string }> | null,
});
root.selected = Retree.link(root.tasks[0]); // ✅ emits on root
root.selected.current.title = "Better docs"; // ✅ emits where task is owned
Retree.parent(root.selected.current) === root.tasks; // truemanaged()#
static managed<TNode>(value): TNode | undefined;Defined in: Retree.ts:981
Resolve a raw value back to its Retree-managed node.
Type Parameters#
| Type Parameter |
|---|
TNode extends object |
Parameters#
| Parameter | Type | Description |
|---|---|---|
value | TNode | Raw object to resolve. |
Returns#
TNode | undefined
The managed node, or undefined when none exists.
Remarks#
This is the inverse of Retree.raw for values that belong to a
Retree tree: given a raw object (for example an element read while
scanning a Retree.raw subtree, or a previous/new value from
a change payload), it returns the latest managed node — ready for
mutation, subscription, or navigation. Passing a managed node returns
its latest managed identity.
Returns undefined when the value has never been materialized as a
Retree node or is not part of a Retree tree; a miss is a normal query
outcome, not an error. Values become materialized when they are read
through a managed node, so scanning via managed proxies first (or using
useRaw's toManaged, which materializes direct children on demand)
guarantees resolution.
Example#
const project = Retree.root({ items: [{ score: 1 }] });
project.items.forEach(() => {}); // materialize
const rawItem = Retree.raw(project.items)[0];
const item = Retree.managed(rawItem);
if (item) item.score = 2; // ✅ emits normallymove()#
Call Signature#
static move<TNode, TValue>(
node,
destination,
key?): TNode;Defined in: Retree.ts:368
Move an existing Retree-managed node from its current parent to a new parent.
Type Parameters
| Type Parameter | Default type |
|---|---|
TNode extends object | - |
TValue extends object | TNode |
Parameters
| Parameter | Type | Description |
|---|---|---|
node | TNode extends TValue ? TNode : never | Existing Retree-managed node to move. |
destination | TValue[] | Retree-managed array, map, set, or object destination. |
key? | number | Insertion index for arrays, map key for maps, or property key for objects. |
Returns
TNode
The latest reproxy for the moved node.
Remarks
Retree is a pure tree: each node has one structural parent. Use move
when ownership should transfer from the old parent to the destination.
Retree finds the current parent with Retree.parent and removes
the node safely before inserting it into the destination.
Arrays accept an optional numeric insertion index. Maps and plain
objects require a key. Sets ignore the key. Do not manually delete the
node from its old parent before calling move.
Example
const workspace = Retree.root({
todo: [{ title: "Docs" }],
done: [] as { title: string }[],
});
const moved = Retree.move(workspace.todo[0], workspace.done);
workspace.todo.length; // 0
workspace.done[0] === moved; // trueCall Signature#
static move<TNode, TKey, TValue>(
node,
destination,
key): TNode;Defined in: Retree.ts:373
Move an existing Retree-managed node from its current parent to a new parent.
Type Parameters
| Type Parameter | Default type |
|---|---|
TNode extends object | - |
TKey | unknown |
TValue extends object | TNode |
Parameters
| Parameter | Type | Description |
|---|---|---|
node | TNode extends TValue ? TNode : never | Existing Retree-managed node to move. |
destination | Map<TKey, TValue> | Retree-managed array, map, set, or object destination. |
key | TKey | Insertion index for arrays, map key for maps, or property key for objects. |
Returns
TNode
The latest reproxy for the moved node.
Remarks
Retree is a pure tree: each node has one structural parent. Use move
when ownership should transfer from the old parent to the destination.
Retree finds the current parent with Retree.parent and removes
the node safely before inserting it into the destination.
Arrays accept an optional numeric insertion index. Maps and plain
objects require a key. Sets ignore the key. Do not manually delete the
node from its old parent before calling move.
Example
const workspace = Retree.root({
todo: [{ title: "Docs" }],
done: [] as { title: string }[],
});
const moved = Retree.move(workspace.todo[0], workspace.done);
workspace.todo.length; // 0
workspace.done[0] === moved; // trueCall Signature#
static move<TNode, TValue>(node, destination): TNode;Defined in: Retree.ts:382
Move an existing Retree-managed node from its current parent to a new parent.
Type Parameters
| Type Parameter | Default type |
|---|---|
TNode extends object | - |
TValue extends object | TNode |
Parameters
| Parameter | Type | Description |
|---|---|---|
node | TNode extends TValue ? TNode : never | Existing Retree-managed node to move. |
destination | Set<TValue> | Retree-managed array, map, set, or object destination. |
Returns
TNode
The latest reproxy for the moved node.
Remarks
Retree is a pure tree: each node has one structural parent. Use move
when ownership should transfer from the old parent to the destination.
Retree finds the current parent with Retree.parent and removes
the node safely before inserting it into the destination.
Arrays accept an optional numeric insertion index. Maps and plain
objects require a key. Sets ignore the key. Do not manually delete the
node from its old parent before calling move.
Example
const workspace = Retree.root({
todo: [{ title: "Docs" }],
done: [] as { title: string }[],
});
const moved = Retree.move(workspace.todo[0], workspace.done);
workspace.todo.length; // 0
workspace.done[0] === moved; // trueCall Signature#
static move<TNode, TDestination>(
node,
destination,
key): TNode;Defined in: Retree.ts:386
Move an existing Retree-managed node from its current parent to a new parent.
Type Parameters
| Type Parameter | Default type |
|---|---|
TNode extends object | - |
TDestination extends object | object |
Parameters
| Parameter | Type | Description |
|---|---|---|
node | TNode | Existing Retree-managed node to move. |
destination | TDestination | Retree-managed array, map, set, or object destination. |
key | RetreeObjectMoveKey<TDestination, TNode> | Insertion index for arrays, map key for maps, or property key for objects. |
Returns
TNode
The latest reproxy for the moved node.
Remarks
Retree is a pure tree: each node has one structural parent. Use move
when ownership should transfer from the old parent to the destination.
Retree finds the current parent with Retree.parent and removes
the node safely before inserting it into the destination.
Arrays accept an optional numeric insertion index. Maps and plain
objects require a key. Sets ignore the key. Do not manually delete the
node from its old parent before calling move.
Example
const workspace = Retree.root({
todo: [{ title: "Docs" }],
done: [] as { title: string }[],
});
const moved = Retree.move(workspace.todo[0], workspace.done);
workspace.todo.length; // 0
workspace.done[0] === moved; // trueon()#
static on<T, TEvent>(
node,
listenerType,
callback): () => void;Defined in: Retree.ts:453
Listen for changes to a node.
Type Parameters#
| Type Parameter | Default type |
|---|---|
T extends object | object |
TEvent extends TRetreeEvents | TRetreeEvents |
Parameters#
| Parameter | Type | Description |
|---|---|---|
node | T | the object to listen for changes to. |
listenerType | TEvent | the type of TRetreeEvents change events to listen to. |
callback | TEvent extends TRetreeChangedEvents ? (reproxiedNode, changes) => void : () => void | the callback function for your listener. |
Returns#
an unsubscribe function to clean up your listeners.
() => void
Remarks#
Use nodeChanged for changes directly owned by the node.
Use treeChanged for changes to the node or descendants.
Use nodeRemoved for when this node is removed from its parent.
Example#
// Create the root node
const counter = Retree.root({ count: 0 });
// Listen for changes to values of the node
const unsubscribe = Retree.on(counter, "nodeChanged", (reproxy) => {
console.log(reproxy !== counter); // output: false
console.log(reproxy.count === counter.count); // output: true
});
// Make a change
counter.count = counter.count + 1;
// Stop listening for changes
unsubscribe();parent()#
static parent(node): object | null;Defined in: Retree.ts:861
Get a parent node for a given child node, if it exists
Parameters#
| Parameter | Type | Description |
|---|---|---|
node | object | a child node to get the parent of |
Returns#
object | null
the parent node if it exists, otherwise null
Example#
const tree = Retree.root({
count: 0,
child: {
count: 0,
child: {
count: 0,
},
},
});
function recursiveLog(node) {
console.log(node.count);
// Get the parent of node, if it exists
const parent = Retree.parent(node);
if (!parent) return; // at top of tree
recursiveLog(parent);
}
Retree.on(tree.child.child, "nodeChanged", (child) => {
// Recursively log the count of this node and all its parents
recursiveLog(child);
});
tree.child.child.count = 1;peekInto()#
static peekInto<TNode, TResult>(node, fn): TResult;Defined in: Retree.ts:1075
Run a read-only query against a node's raw object at native speed, then resolve the result back to its Retree-managed node when one exists.
Type Parameters#
| Type Parameter |
|---|
TNode extends object |
TResult |
Parameters#
| Parameter | Type | Description |
|---|---|---|
node | TNode | Retree-managed node to query. |
fn | (raw) => TResult | Read-only callback that receives the raw object behind node. |
Returns#
TResult
The callback result, resolved to its managed node when one exists.
Remarks#
peekInto combines Retree.raw and Retree.untracked:
the callback receives the raw object behind node, so every read
inside it skips proxy traps and dependency tracking. If the callback
returns an object that belongs to a Retree tree, the latest managed
node (reproxy, or base proxy when the node has never reproxied) is
returned instead, ready for mutation or subscription. Primitives,
null, undefined, and unmanaged objects are returned as-is.
Only the returned value itself is resolved. A container built inside
the callback — a filter result, a tuple, an object literal — is
returned unchanged with raw elements; resolve elements individually
when they must be managed. Children that have never been read through
the managed tree are not yet materialized and resolve to their raw
value; traverse the path once, or use prepareTree / autoPrepare,
when a managed result is required.
Example#
const project = Retree.root({
tasks: [
{ id: "a", done: false },
{ id: "b", done: true },
],
});
project.tasks.forEach(() => {}); // materialize once (or prepareTree)
const task = Retree.peekInto(project.tasks, (rawTasks) =>
rawTasks.find((candidate) => candidate.id === "b")
);
// `task` is the managed node: mutations emit normally.
if (task) task.done = false; // ✅ emits
const doneCount = Retree.peekInto(
project.tasks,
(rawTasks) => rawTasks.filter((candidate) => candidate.done).length
); // ✅ primitive result returned as-israw()#
static raw<TNode>(node): TNode;Defined in: Retree.ts:945
Get the raw, unproxied object behind a Retree-managed node for read-only, non-reactive access.
Type Parameters#
| Type Parameter |
|---|
TNode extends object |
Parameters#
| Parameter | Type | Description |
|---|---|---|
node | TNode | Retree-managed node to unwrap. |
Returns#
TNode
The raw object behind the node.
Remarks#
Reads through Retree proxies pay per-property trap overhead. That is usually irrelevant, but algorithms that scan large collections of deeply nested nodes can read the raw object at native speed instead.
Treat the returned object as read-only. Mutating it directly skips
Retree change emission and can desynchronize memoized comparisons; make
all writes through the managed node. Reads through raw are invisible
to reactivity: they are not trapped by useSelect/Retree.select
selectors or @memo dependency collection, and children read this way
are not prepared for Retree.parent / Retree.on usage.
Raw purity guarantee: the returned subtree contains zero Retree
proxies, under every write path — including reparenting assignments,
Retree.move, Map/Set value reads, and post-construction assignment of
class instances or collections. Every value is plain data, every read
is native speed, and structuredClone(Retree.raw(node)) is a valid
point-in-time copy. Use Retree.managed to resolve a raw value
back to its managed node.
Throws when the value is not a Retree-managed node. When a value may
come from either side of the proxy boundary, guard with
Retree.isNode: Retree.isNode(value) ? Retree.raw(value) : value.
Example#
const project = Retree.root({ items: [{ score: 1 }, { score: 92 }] });
const rawItems = Retree.raw(project.items);
const total = rawItems.reduce((sum, item) => sum + item.score, 0); // ✅ native-speed read
project.items[0].score = 50; // ✅ writes stay on the managed treeregisterRootName()#
static registerRootName(node, name): void;Defined in: Retree.ts:813
Register a display name for a Retree root node.
Parameters#
| Parameter | Type | Description |
|---|---|---|
node | object | A Retree-managed node; typically the object returned by Retree.root. The name attaches to the node's tree root identity, so pass the root itself. |
name | string | Display name for the tree. |
Returns#
void
Remarks#
Named roots exist for tooling: debug taps report the name of the tree
a change happened in, and @retreejs/devtools uses the registry to
enumerate and label live trees. Naming a root changes no runtime
behavior and does not keep the tree alive (the registry holds it
weakly).
Re-registering the same node replaces its name; re-registering the same name points it at the new node.
Example#
const settings = Retree.root({ theme: "dark" });
Retree.registerRootName(settings, "settings");root()#
static root<T>(object): T;Defined in: Retree.ts:265
Builds a Retree compatible root node for the root object of your tree.
Type Parameters#
| Type Parameter | Default type |
|---|---|
T extends object | object |
Parameters#
| Parameter | Type | Description |
|---|---|---|
object | T | a root TreeNode for your tree |
Returns#
T
a Retree compatible object of type T
Remarks#
Use this once where plain state enters Retree. The returned proxy is
compatible with Retree.on, Retree.parent,
Retree.move, Retree.link, and React hooks from
@retreejs/react.
Do mutate the returned tree directly with normal JavaScript assignment
and collection methods. Do not call Retree.root(...) on every child
you assign into the tree; Retree prepares children as they are attached
or read.
Example#
const counter = Retree.root({ count: 0 });
Retree.on(counter, "nodeChanged", () => console.log(counter.count));
counter.count = counter.count + 1;runSilent()#
static runSilent(transaction, skipReproxy?): void;Defined in: Retree.ts:1134
Run a synchronous transaction that will not cause `Retree.on listeners to emit.
Parameters#
| Parameter | Type | Default value | Description |
|---|---|---|---|
transaction | () => void | undefined | transaction function to run |
skipReproxy | boolean | true | skip reproxying nodes such that subsequent comparisons are equal. defaults to true. |
Returns#
void
Remarks#
Use runSilent for non-rendered bookkeeping or integration state that
should update without notifying Retree listeners. By default it also
skips reproxying, so old and new object identities stay equal for later
comparison checks.
Pass skipReproxy = false when you want to suppress listener emission
but still refresh reproxy identities.
Silence means silence, in both modes. The only difference between
skipReproxy = true and skipReproxy = false is whether reproxy
identities (and parent identities) refresh. Everything observable is
suppressed identically in both modes: Retree.on listeners do not
emit, ReactiveNode lifecycle does not run (onChanged is not
called), dependency subscriptions are not refreshed, and dependents of
changed dependencies are not notified. If a silent write changes what
a ReactiveNode.dependencies getter returns, subscriptions resync on
that node's next non-silent change.
Example#
const state = Retree.root({ rendered: 0, telemetry: 0 });
Retree.on(state, "nodeChanged", () => console.log("render"));
Retree.runSilent(() => {
state.telemetry += 1;
}); // ❌ no listener emit
state.rendered += 1; // ✅ emitsrunTransaction()#
static runTransaction(transaction): void;Defined in: Retree.ts:1170
Run a synchronous transaction that will not cause Retree.on listeners for changed nodes to emit multiple times.
Parameters#
| Parameter | Type | Description |
|---|---|---|
transaction | () => void | transaction function to run |
Returns#
void
Remarks#
If multiple nodes changed during the transaction, Retree.on events will be emitted for each node that changed.
If using React, this should still be flattened to a single render, but it is not guaranteed.
It may be reasonable to combine this with React.startTransition if this is a concern.
Example#
const counter = Retree.root({ count: 0 });
Retree.on(counter, "nodeChanged", () => console.log(counter.count));
// Will only emit "nodeChanged" once
Retree.runTransaction(() => {
counter.count = counter.count + 1;
counter.count = counter.count * 2;
});select()#
Call Signature#
static select<TNode, TSelected>(
node,
selector,
callback,
options?): () => void;Defined in: Retree.ts:584
Subscribe to a derived value from any Retree-managed node.
Type Parameters
| Type Parameter |
|---|
TNode extends object |
TSelected |
Parameters
| Parameter | Type | Description |
|---|---|---|
node | TNode | Retree-managed node to observe. |
selector | RetreeSelectSelector<TNode, TSelected> | Function that reads a selected value or dependency list from the latest reproxy. |
callback | (next, previous) => void | Called only when the selected value or dependency list changes. |
options? | RetreeSelectOptions<TSelected> | Optional listener type and equality comparison for the whole selected value or tuple. |
Returns
Unsubscribe function.
() => void
Remarks
select recomputes the selected value when the observed node or selected
reactive dependencies emit, then calls callback only when the selection
changes. Selectors may return one value or an ordered dependency list.
Reactive entries in a dependency list are subscribed to; primitive and
plain entries are compared by identity.
Dependency-list subscriptions are observational: if a selected dependency
emits, select calls your callback when the selection changes, but it
does not force the node passed to select to receive a fresh reproxy.
Use @select when a ReactiveNode owner should emit nodeChanged.
This is a subscription primitive, not a memo cache: use memo /
fnMemo to cache computation, and select to narrow notifications.
By default select listens to nodeChanged, which is correct when the
selector reads fields directly owned by node. Pass
listenerType: "treeChanged" when the selector intentionally reads
descendants that are not included as reactive entries in a dependency
list.
You can also call Retree.select(() => value, callback) without a node.
That form traps reads automatically. Whole Retree-managed values are
subscribed to broadly. Property reads subscribe to the owner node but
compare the specific property value, so task.done can react to task
replacement or done changes without reacting to unrelated task fields.
Primitive reads are kept as comparison values, so the callback only runs
when the trapped reads make the selected value or dependency set change.
Examples
const project = Retree.root({
tasks: [{ done: false }, { done: true }],
});
const unsubscribe = Retree.select(
project.tasks,
(tasks) => tasks.filter((task) => task.done).length,
(next, previous) => console.log({ next, previous }),
{ listenerType: "treeChanged" }
);
project.tasks[0].done = true; // ✅ callback: 1 -> 2
project.tasks[0].done = true; // ❌ selected value did not change
unsubscribe();Retree.select(
row,
(self) => [self.attributes, self.attributeId, self.attribute],
([, , attribute]) => console.log(attribute)
);Retree.select(
() => project.tasks.filter((task) => task.done).length,
(doneCount) => console.log(doneCount)
);Call Signature#
static select<TSelector>(
selector,
callback,
options?): () => void;Defined in: Retree.ts:590
Subscribe to a derived value from any Retree-managed node.
Type Parameters
| Type Parameter |
|---|
TSelector extends RetreeTrackedSelectSelector<unknown> |
Parameters
| Parameter | Type | Description |
|---|---|---|
selector | TSelector | Function that reads a selected value or dependency list from the latest reproxy. |
callback | (next, previous) => void | Called only when the selected value or dependency list changes. |
options? | RetreeSelectOptions<ReturnType<TSelector>> | Optional listener type and equality comparison for the whole selected value or tuple. |
Returns
Unsubscribe function.
() => void
Remarks
select recomputes the selected value when the observed node or selected
reactive dependencies emit, then calls callback only when the selection
changes. Selectors may return one value or an ordered dependency list.
Reactive entries in a dependency list are subscribed to; primitive and
plain entries are compared by identity.
Dependency-list subscriptions are observational: if a selected dependency
emits, select calls your callback when the selection changes, but it
does not force the node passed to select to receive a fresh reproxy.
Use @select when a ReactiveNode owner should emit nodeChanged.
This is a subscription primitive, not a memo cache: use memo /
fnMemo to cache computation, and select to narrow notifications.
By default select listens to nodeChanged, which is correct when the
selector reads fields directly owned by node. Pass
listenerType: "treeChanged" when the selector intentionally reads
descendants that are not included as reactive entries in a dependency
list.
You can also call Retree.select(() => value, callback) without a node.
That form traps reads automatically. Whole Retree-managed values are
subscribed to broadly. Property reads subscribe to the owner node but
compare the specific property value, so task.done can react to task
replacement or done changes without reacting to unrelated task fields.
Primitive reads are kept as comparison values, so the callback only runs
when the trapped reads make the selected value or dependency set change.
Examples
const project = Retree.root({
tasks: [{ done: false }, { done: true }],
});
const unsubscribe = Retree.select(
project.tasks,
(tasks) => tasks.filter((task) => task.done).length,
(next, previous) => console.log({ next, previous }),
{ listenerType: "treeChanged" }
);
project.tasks[0].done = true; // ✅ callback: 1 -> 2
project.tasks[0].done = true; // ❌ selected value did not change
unsubscribe();Retree.select(
row,
(self) => [self.attributes, self.attributeId, self.attribute],
([, , attribute]) => console.log(attribute)
);Retree.select(
() => project.tasks.filter((task) => task.done).length,
(doneCount) => console.log(doneCount)
);untracked()#
static untracked<T>(fn): T;Defined in: Retree.ts:1022
Run a synchronous function with Retree dependency tracking paused.
Type Parameters#
| Type Parameter |
|---|
T |
Parameters#
| Parameter | Type | Description |
|---|---|---|
fn | () => T | Function to run without dependency tracking. |
Returns#
T
The function's return value.
Remarks#
Inside tracked contexts — Retree.select(() => ...), useSelect
selectors, and auto-trapped @memo / @fnMemo / @select bodies —
every Retree read is recorded as a dependency. Wrap bulk reads in
untracked when they should not subscribe, such as a wide scan whose
result is already covered by a narrower dependency.
Reads inside untracked still go through Retree proxies (combine with
Retree.raw for native-speed scans). Writes inside untracked
still emit normally; this pauses dependency collection, not change
emission.
Example#
const doneCount = Retree.select(
() => {
const tasks = project.tasks; // ✅ tracked: subscribes to tasks
return Retree.untracked(
() => tasks.filter((task) => task.done).length
);
},
(count) => console.log(count)
);use()#
static use<T>(object): T;Defined in: Retree.ts:238
Type Parameters#
| Type Parameter | Default type |
|---|---|
T extends object | object |
Parameters#
| Parameter | Type |
|---|---|
object | T |
Returns#
T
Deprecated#
Use root instead.
Example#
const state = Retree.use({ count: 0 }); // deprecated
const state = Retree.root({ count: 0 }); // preferred