Skip to content

Let an explicit equals decide when tracked selections notify - #113

Merged
ryanbliss merged 1 commit into
mainfrom
fix/equals-gates-tracked-select
Sep 8, 2026
Merged

ryanbliss merged 1 commit into
mainfrom
fix/equals-gates-tracked-select

Conversation

@ryanbliss

Copy link
Copy Markdown
Owner

Problem

The tracked forms of Retree.select(selector, onChange, { equals }) and useSelect(selector, { equals }) notified and re-rendered whenever a tracked dependency changed, even when equals reported the previous and next selections equal. createRetreeTrackedSelectionObserver.evaluate emitted on dependenciesChanged regardless of equals, and refreshTrackedSelectState minted a new container unless the selection, reads and sources were all unchanged.

Neo hit this with a list grid that selects the projected row order under a localized-text sort with equals: sameEntryIds. An edit to a graded text that leaves the order alone re-ran the selector (correct) and then re-rendered the whole grid (wrong), when only the cell showing that text should update.

Solution

The dependency check exists so a selector returning a stable node reference still notifies when that node's content changes. It now applies only to the default comparison. A new shared helper, hasTrackedSelectionChanged, holds that rule for both forms: with equals supplied it is the whole answer; without it, a reference change or a reads change counts. The React tracked form now keeps its container on the same condition as the node form (selectedEqual && sourcesEqual), still swapping sources and refreshing its snapshot when the reads move.

Specs cover both forms: a rank write that re-runs a sorted-ids selector without changing the order neither notifies nor re-renders, and a write that reorders does. Both fail on main.

Alternatives

  • Ask callers to guard onChange or memoize around useSelect themselves. That leaves equals documented as stabilizing outputs while it does not, and useSelect has no hook to suppress a re-render after the fact.
  • Drop the dependency emission entirely. That breaks selectors that return a node and rely on content changes notifying, which the existing specs cover.

Verification

npm run typecheck, npm run test (1699 passing), npm run doctor, npm run lint:architecture.

The tracked forms of Retree.select and useSelect notified and re-rendered
when a dependency changed even when the caller's equals reported the
previous and next selections equal. The dependency check exists so a
selector returning a stable node reference still notifies when that
node's content changes; it now applies only to the default comparison.
With equals supplied, an equal re-run keeps the previous reference and
stays silent.
@ryanbliss
ryanbliss force-pushed the fix/equals-gates-tracked-select branch from b91570d to 36a2f1c Compare September 8, 2026 22:04
@ryanbliss
ryanbliss merged commit c2499cb into main Sep 8, 2026
3 checks passed
@ryanbliss
ryanbliss deleted the fix/equals-gates-tracked-select branch September 8, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant