retree
DocsAPIWhy Retree

Start here

  • Quickstart
  • Thinking in Retree
  • Common pitfalls

React

  • Choosing a hook
  • useRoot
  • useNode
  • useTree
  • useSelect
  • useRaw

Core

  • Events & subscriptions
  • Tree operations
  • Transactions & silent writes

View models

  • ReactiveNode & decorators
  • Setup & decorators

Going deeper

  • Performance
  • Convex integration

Start here

  • Quickstart
  • Thinking in Retree
  • Common pitfalls

React

  • Choosing a hook
  • useRoot
  • useNode
  • useTree
  • useSelect
  • useRaw

Core

  • Events & subscriptions
  • Tree operations
  • Transactions & silent writes

View models

  • ReactiveNode & decorators
  • Setup & decorators

Going deeper

  • Performance
  • Convex integration
Loading page…

retree

Reactive object trees for React. MIT licensed.

© 2026 Ryan Bliss

Docs

  • Quickstart
  • Thinking in Retree
  • React hooks
  • Common pitfalls

Reference

  • @retreejs/core
  • @retreejs/react
  • @retreejs/convex
  • @retreejs/react-convex

Project

  • Why Retree
  • GitHub
  • npm
  • llms.txt

Docs

Edit on GitHub

Choosing a hook

useNode, useTree, useSelect, useRaw, and useRoot subscribe to the same tree with different granularity. Pick the narrowest one that answers "when should this component re-render?"

Every Retree hook answers the same question — when should this component re-render? — at a different granularity. They all read the same tree; the only thing you are choosing is the subscription.

The decision table#

HookRe-renders whenUse it forAvoid when
useNodedirect nodeChanged events on one node — its own fields changerows, panels, forms, and focused child componentsthe component only needs a derived value — reach for useSelect
useTreetreeChanged events from a node or any descendantsmall subtrees that truly need broad invalidationhot paths and large app-level roots
useSelecta selected value or ordered dependency list changescounts, totals, booleans, and other narrow projections — including ones computed across a deep subtreeyou want to cache expensive computation — it is a subscription primitive, not a memo cache
useRawsame events as useNode (nodeChanged by default), but you read rawcomponents that read wide during render: big tables, canvas layers, serializationchildren need node props and you skip toManaged, or you write to raw values
useRootnever by itself — it creates a root; pair it with a subscribing hookstate that belongs to one React subtree, for that component's lifetimestate that should outlive the component — create a module-scope Retree.root instead

Try it#

The playground below runs one shared tree — project.tasks — through each hook. The render counters are real: switch modes, press the same mutation buttons, and compare which components re-render.

useNode: the panel subscribes to the tasks array, each row to its own task — toggle and rename re-render one row; add re-renders the panel.

useNode(project.tasks)renders: 1

done: 1/2 (computed here — goes stale on toggle; that's a job for useSelect)

  • renders: 1
  • renders: 1

Subscription boundary

project
tasks
tasks[0]
tasks[1]

boundary: nodeChanged subscription on tasks, plus one per row task

The narrowest subscription wins#

Retree performs best when components subscribe to the narrowest node or value they need:

  • Prefer useNode(child) for item rows and focused panels.
  • Prefer useSelect(node, selector) for selected values or dependency lists that should only re-render when the selection changes.
  • Treat useTree (and treeChanged in general) as broad subtree invalidation, especially in hot paths.
  • Use useRaw when the render body itself reads wide — the subscription stays as narrow as useNode, but reads skip the proxy.
  • Avoid constructing Retree roots during render; create them at module scope or with useRoot.

When two hooks would both work, pick the one further up this list. The subscription boundaries should follow your component boundaries — that idea is developed in Thinking in Retree, and the first-hour mistakes are catalogued in Common pitfalls.

The five hooks#

  • useRoot — create a root for a component lifetime.
  • useNode — subscribe to one node's own fields.
  • useTree — subscribe to a node and all descendants.
  • useSelect — subscribe to a selected value.
  • useRaw — subscribe like useNode, read proxy-free.

Full signatures live in the API reference.

← PreviousCommon pitfallsNext →useRoot

On this page

  • The decision table
  • Try it
  • The narrowest subscription wins
  • The five hooks