API reference · generated from source
Read the guide →Function: useSelect()#
Call Signature#
function useSelect<TSelected>(selector, options?): TSelected;Defined in: useSelect.ts:681
Subscribe to a selected value from any Retree-managed node.
Type Parameters#
| Type Parameter |
|---|
TSelected |
Parameters#
| Parameter | Type | Description |
|---|---|---|
selector | () => TSelected | Function that reads a selected value or dependency list from the latest reproxy. |
options? | UseSelectOptions<TSelected> | Optional listener type and equality comparison. |
Returns#
TSelected
The latest selected value.
Remarks#
useSelect narrows React updates to changes in the selected value or
ordered dependency list. Reactive entries in a dependency list are
subscribed to; primitive and plain entries are compared by identity. It is a
subscription primitive, not a memo cache: use memo / fnMemo for caching
computation and useSelect for reducing re-renders.
By default useSelect listens to nodeChanged, which is best when the
selector reads fields directly owned by the node. Pass
listenerType: "treeChanged" when the selector reads descendants that are
not included as reactive entries in a dependency list. Pass equals when
the selected value or tuple should be compared structurally.
Dependency-list subscriptions are observational: if a tuple entry changes,
useSelect may re-render this component, but it does not force the node
passed to useSelect to receive a fresh reproxy. Use @select on a
ReactiveNode getter when the owner node itself should emit nodeChanged.
You can also call useSelect(() => value) 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.
The selector is memoized per hook instance: it re-runs when a subscribed
source changes, when the observed node changes, or when the selector's
function identity changes between renders. An inline selector gets a fresh
identity every render, so it re-runs once per render and may safely close
over props and other render-scoped values. A hoisted or useCallback-stable
selector skips unrelated parent re-renders entirely — so it must not close
over values that change between renders (other than the node and
Retree-managed reads); include such captures in the useCallback dependency
list so the selector identity changes with them. Changing only the equals
function identity never re-runs the selector: equals is consulted when a
recompute happens, to stabilize outputs.
Do not use useSelect to cache expensive computation for reuse elsewhere.
Put that work behind memo, @memo, or @fnMemo, then select the cached
value.
The node form's first argument is the Retree-managed node or node factory to
observe. An inline factory like useSelect(() => Retree.root({ ... }), ...)
re-runs every render and silently resets state: hoist the factory (and its
Retree.root call) outside the component, or use useRoot.
Examples#
import { Retree } from "@retreejs/core";
import { useSelect } from "@retreejs/react";
const project = Retree.root({
tasks: [
{ title: "Docs", done: false },
{ title: "Tests", done: true },
],
});
function DoneCount() {
const doneCount = useSelect(
project.tasks,
(tasks) => tasks.filter((task) => task.done).length,
{ listenerType: "treeChanged" }
);
return <span>{doneCount}</span>;
}
project.tasks[0].done = true; // ✅ re-renders DoneCount
project.tasks[0].title = "Better docs"; // ❌ no re-renderconst [, , attribute] = useSelect(row, (self) => [
self.attributes,
self.attributeId,
self.attribute,
]);function DoneCount() {
const doneCount = useSelect(() =>
project.tasks.filter((task) => task.done).length
);
return <span>{doneCount}</span>;
}Call Signature#
function useSelect<TNode, TSelected>(
node,
selector,
options?): TSelected;Defined in: useSelect.ts:687
Subscribe to a selected value from any Retree-managed node.
Type Parameters#
| Type Parameter |
|---|
TNode extends object |
TSelected |
Parameters#
| Parameter | Type | Description |
|---|---|---|
node | | TNode | NodeFactory<TNode> | - |
selector | (node) => TSelected | Function that reads a selected value or dependency list from the latest reproxy. |
options? | UseSelectOptions<TSelected> | Optional listener type and equality comparison. |
Returns#
TSelected
The latest selected value.
Remarks#
useSelect narrows React updates to changes in the selected value or
ordered dependency list. Reactive entries in a dependency list are
subscribed to; primitive and plain entries are compared by identity. It is a
subscription primitive, not a memo cache: use memo / fnMemo for caching
computation and useSelect for reducing re-renders.
By default useSelect listens to nodeChanged, which is best when the
selector reads fields directly owned by the node. Pass
listenerType: "treeChanged" when the selector reads descendants that are
not included as reactive entries in a dependency list. Pass equals when
the selected value or tuple should be compared structurally.
Dependency-list subscriptions are observational: if a tuple entry changes,
useSelect may re-render this component, but it does not force the node
passed to useSelect to receive a fresh reproxy. Use @select on a
ReactiveNode getter when the owner node itself should emit nodeChanged.
You can also call useSelect(() => value) 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.
The selector is memoized per hook instance: it re-runs when a subscribed
source changes, when the observed node changes, or when the selector's
function identity changes between renders. An inline selector gets a fresh
identity every render, so it re-runs once per render and may safely close
over props and other render-scoped values. A hoisted or useCallback-stable
selector skips unrelated parent re-renders entirely — so it must not close
over values that change between renders (other than the node and
Retree-managed reads); include such captures in the useCallback dependency
list so the selector identity changes with them. Changing only the equals
function identity never re-runs the selector: equals is consulted when a
recompute happens, to stabilize outputs.
Do not use useSelect to cache expensive computation for reuse elsewhere.
Put that work behind memo, @memo, or @fnMemo, then select the cached
value.
The node form's first argument is the Retree-managed node or node factory to
observe. An inline factory like useSelect(() => Retree.root({ ... }), ...)
re-runs every render and silently resets state: hoist the factory (and its
Retree.root call) outside the component, or use useRoot.
Examples#
import { Retree } from "@retreejs/core";
import { useSelect } from "@retreejs/react";
const project = Retree.root({
tasks: [
{ title: "Docs", done: false },
{ title: "Tests", done: true },
],
});
function DoneCount() {
const doneCount = useSelect(
project.tasks,
(tasks) => tasks.filter((task) => task.done).length,
{ listenerType: "treeChanged" }
);
return <span>{doneCount}</span>;
}
project.tasks[0].done = true; // ✅ re-renders DoneCount
project.tasks[0].title = "Better docs"; // ❌ no re-renderconst [, , attribute] = useSelect(row, (self) => [
self.attributes,
self.attributeId,
self.attribute,
]);function DoneCount() {
const doneCount = useSelect(() =>
project.tasks.filter((task) => task.done).length
);
return <span>{doneCount}</span>;
}