Skip to content

feat(joint-react): add useGraphState and useOnGraphStateChange - #3532

Open
samuelgja wants to merge 18 commits into
clientIO:devfrom
samuelgja:feat/new-react-measure-module
Open

samuelgja wants to merge 18 commits into
clientIO:devfrom
samuelgja:feat/new-react-measure-module

Conversation

@samuelgja

@samuelgja samuelgja commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Description

Expose graph state through useGraphState(selector, isEqual?), with useOnGraphStateChange() and useOnCellsChange() for callbacks without rerendering. Selectors subscribe only to the cells, collections, or sizing version they read. Public exports include GraphState, selector/callback types, selectGraphSizingVersion, and selectIsGraphSizingComplete.

useOnElementsMeasured remains supported and is deprecated in favor of the general graph-state API. The sizing selectors return zero/incomplete after the last element is removed; the legacy hook keeps its existing initialization contract: empty/re-add is an ordinary change, while a graph reset starts a new history.

The branch is synchronized with current dev, including the released measurement fixes from master. It preserves first-report waiting, retained-node remeasurement after reset/re-add, late subscription and reentrant reset handling, render-idle settling, and deferred-transaction notification ordering. Original measurement regression suites are retained alongside the new graph-state suites.

Verified selector regressions are also corrected:

  • Cached callback identity uses Object.is, handling NaN and signed zero consistently.
  • Changing an equality function refreshes the selection without resubscribing.
  • Detached collection snapshots belong to each reader, so a fresh hook mount reads current model attributes.
  • Scalar cell IDs and array targets use distinct keys, including IDs that resemble the key prefixes.
  • Same-ID detached model replacements refresh collection readers while graph-bound records retain precedence.

Stories demonstrate the general hooks and selectors, including the measured-state example. Changesets cover the new API, deprecation, and fixes affecting existing useCells behavior.

Validation

  • Full workspace distribution build passed.
  • Full root yarn test passed on the repository-pinned Node 22.14.0, including React 19/compiler and React 18 suites, plus core, layout, routing, sanitizer and mock-SVG suites.
  • Root lint passed with existing story warnings and expected deprecation warnings in legacy-hook coverage.
  • Eight prior selector regressions failed before their fixes and passed afterwards.
  • Ten additional target/collection regressions cover selector-free return shapes, callback retargeting, and detached versus graph-bound replacements; the corresponding failure paths were reproduced before their fixes.
  • Changeset status against dev passed.
  • All 44 React benchmark cases passed.

On macOS the test browser was launched with process-local classic-scrollbar flags to satisfy the existing viewport test's scrollbar assumption. Test assertions and repository browser configuration were not changed. The host's Node 24 run encountered a worker crash; the final complete run passed on Node 22.14.0.

- Introduced a new measurement generation system in GraphStore to track resets and initial measurement passes.
- Updated `useOnElementsMeasured` to deliver events based on settled changes, ensuring accurate measurement reporting.
- Modified the measurement observer to handle cases where elements are added without sizes and to trigger events when they are measured.
- Improved test coverage for measurement events, including scenarios for element resets and size changes.
- Refactored size change handling to differentiate between application-resized elements and those measured by the observer.
- Ensured that the `markElementRendered` function correctly settles elements that arrive without sizes.
…ed elements

- Updated the measurement handling logic in useOnElementsMeasured to account for elements that are not rendered by the paper or are zero-sized.
- Introduced new tests to cover scenarios where elements are culled or removed before measurement.
- Improved the flush function to ensure all rendering and measurement processes are settled before events are delivered.
- Refactored GraphStore to manage outstanding elements more effectively, ensuring that elements not rendered by any paper do not block measurement events.
- Added a settleUnrenderedElements method to clear outstanding elements that are not accounted for by any paper.
- Enhanced documentation to clarify the behavior of the measurement system regarding application-written sizes and unmeasured elements.
- Removed the export of create-elements-size-observer from index.ts.
- Introduced a new measurement.ts file to manage element size measurements.
- Updated paper-store.ts to integrate with the new measurement system.
- Modified state-container.ts to adjust import paths for selector utilities.
- Enhanced scheduler tests to handle callback errors and cascading callbacks.
- Replaced useOnElementsMeasured with useOnCellsChange in various examples and stories to reflect the new measurement approach.
- Added warnings for selector misuse and cascading callbacks in dev-warnings.ts.
- Updated examples to utilize the new selectMeasuredState selector for layout adjustments based on element sizes.
@samuelgja
samuelgja requested review from kumilingus and a balanced review from Copilot and removed request for Copilot October 7, 2026 07:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Selection deduplication mishandles NaN, and the feature changeset violates the repository’s single-line format.

Review effort: Balanced
Findings: 1 Medium severity · 2 Low severity

Open (3)
What changed in this PR

Centralizes @joint/react measurement handling, adds measurement selectors and useOnCellsChange, and improves callback scheduling reliability.

Changes:

  • Introduces the measurement controller, selectors, and effect-like cell change hook.
  • Migrates stories and deprecates useOnElementsMeasured.
  • Hardens scheduler and graph projection behavior with expanded tests.
File Description
stories/​examples/​measured-state/​story.tsx Registers the measurement example.
stories/​examples/​measured-state/​code.tsx Demonstrates the new selectors and hook.
stories/​examples/​introduction/​code.tsx Migrates minimap measurement handling.
stories/​examples/​flowchart/​code.tsx Fits measured flowchart content.
stories/​examples/​element-controls/​story.tsx Updates the API documentation link.
stories/​examples/​element-controls/​code.tsx Migrates control initialization.
stories/​examples/​dynamic-status-icons/​story.tsx Updates the API documentation link.
stories/​examples/​dynamic-status-icons/​code.tsx Migrates status-list initialization.
stories/​examples/​collapsible-subtrees/​code.tsx Migrates initial tree layout.
stories/​examples/​automatic-layout/​story.tsx Updates the API documentation link.
stories/​examples/​automatic-layout/​code.tsx Uses measured-state change callbacks.
stories/​examples/​automatic-layout-storage/​story.tsx Updates the API documentation link.
stories/​examples/​automatic-layout-storage/​code.tsx Migrates persistent-layout measurement handling.
src/​utils/​scheduler.ts Isolates failures and bounds cascades.
src/​utils/​dev-warnings.ts Adds selector and cascade warnings.
src/​utils/​__tests__/​scheduler.test.ts Tests failure and cascade handling.
src/​store/​state-container.ts Updates selector utility imports.
src/​store/​paper-store.ts Connects papers to measurement settling.
src/​store/​measurement.ts Centralizes measurement state and observation.
src/​store/​index.ts Removes the obsolete observer export.
src/​store/​graph-store.ts Delegates measurement to the controller.
src/​store/​graph-projection.ts Preserves reentrant graph changes.
src/​store/​graph-changes.ts Removes measurement-specific forwarding.
src/​store/​create-elements-size-observer.ts Deletes the superseded observer.
src/​store/​clear-view.ts Removes unused view helpers.
src/​store/​__tests__/​measurement.test.ts Covers the measurement controller.
src/​store/​__tests__/​graph-store.test.ts Updates measurement integration tests.
src/​store/​__tests__/​graph-store-features.test.ts Uses the new observer interface.
src/​store/​__tests__/​graph-changes.test.ts Removes obsolete callback tests.
src/​store/​__tests__/​create-elements-size-observer.test.ts Deletes superseded observer tests.
src/​store/​__tests__/​clear-view.test.ts Removes deleted-helper tests.
src/​state/​data-mapping/​cell-record-merge.ts Updates selector utility imports.
src/​selectors/​store-selectors.ts Extracts internal store selectors.
src/​selectors/​source-selector.ts Adds store-backed selector infrastructure.
src/​selectors/​selector-utils.ts Relocates equality helpers.
src/​selectors/​measurement-selectors.ts Adds public measurement selectors.
src/​selectors/​index.ts Exports reorganized selectors.
src/​selectors/​__tests__/​store-selectors.test.ts Updates internal selector tests.
src/​selectors/​__tests__/​source-selector.test.ts Tests source-selector behavior.
src/​selectors/​__tests__/​selector-utils.test.ts Adds equality-helper coverage.
src/​internal.ts Preserves internal auto-size exports.
src/​index.ts Exports the new public API.
src/​hooks/​use-on-elements-measured.ts Deprecates and reimplements the old hook.
src/​hooks/​use-on-cells-change.ts Adds the effect-like change hook.
src/​hooks/​use-measure-element.tsx Uses centralized measurement and cache clearing.
src/​hooks/​use-create-portal-paper.tsx Removes global measured-state rendering.
src/​hooks/​use-cells.ts Shares source selection and subscriptions.
src/​hooks/​use-are-elements-measured.ts Deletes the obsolete internal hook.
src/​hooks/​index.ts Exports useOnCellsChange.
src/​hooks/​__tests__/​use-on-elements-measured.test.tsx Updates deprecated-hook tests.
src/​hooks/​__tests__/​use-on-cells-change.test.tsx Covers the new hook and selectors.
src/​hooks/​__tests__/​use-measure-element.test.tsx Tests targeted cache clearing.
src/​hooks/​__tests__/​measurement-settling.test.tsx Covers additional settling cases.
src/​hooks/​__tests__/​measurement-events.test.tsx Ports settled-event specifications.
src/​components/​paper/​render-element/​paper-element-item.tsx Reports committed element content.
src/​components/​paper/​render-element/​__tests__/​paper-element-item.test.tsx Updates portal-item tests.
src/​components/​graph/​graph-provider.tsx Updates measurement type imports.
src/​components/​graph/​__tests__/​graph-provider-hot-reload.test.tsx Migrates hot-reload measurement coverage.
.changeset/​react-store-callbacks.md Records scheduler and callback fixes.
.changeset/​react-on-cells-change.md Records the new hook and selectors.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/joint-react/src/hooks/use-on-cells-change.ts Outdated
Comment thread .changeset/react-on-cells-change.md Outdated
Comment thread packages/joint-react/src/hooks/__tests__/use-on-elements-measured.test.tsx Outdated
- Added `graph-state.ts` to manage the state of the graph, including cell records and sizing version.
- Updated measurement logic in `measurement.ts` to use a versioning system instead of a state flag.
- Removed the deprecated `collection-subscription.ts` utility.
- Refactored various components to utilize the new `useGraphState` and `useOnGraphStateChange` hooks.
- Updated examples and stories to reflect changes in state management and measurement handling.
- Enhanced dev warnings for reading graph state outside of selectors.
…s commit

- measurement: deliver one scheduler round after the graph events that settle
  an element, so a subscriber of the new sizing version reads committed cells
- measurement: re-check the delivery when a paper is removed, so elements that
  only waited for the last paper settle
- useCells / useOnCellsChange: name the called hook in the unstable-selector
  dev warning; compute the target key once per render
- docs: StrictMode mount call note, examples on the callback and selector types
…seOnElementsMeasured

- store/measurement.ts replaces create-elements-size-observer.ts and the
  measurement code in graph-store, graph-changes and graph-projection; the
  ResizeObserver is created by the first observe()
- useOnElementsMeasured: one event per settled change, a removal included;
  zero-sized elements count as measured; elements of an external graph are
  tracked from the start; an unmounted measured node no longer blocks events;
  the event is announced once the measured sizes are in the store
- useCells runs on an internal graph-state reader that subscribes to what its
  selector reads; an inline selector capturing a changed prop is re-run
- scheduler: a throwing callback no longer stops the flush; cascading
  callbacks continue on a later task; a graph change made by a subscriber
  during notification is no longer dropped
…re-module

Stack the new hooks and selectors on the measurement fixes: this branch now
adds only the public API (useGraphState, useOnGraphStateChange,
useOnCellsChange, the sizing selectors) and the useOnElementsMeasured
deprecation on top of fix/react-measurement-module.
@samuelgja samuelgja changed the title feat(joint-react): add useOnCellsChange and measurement selectors feat(joint-react): add useGraphState and useOnGraphStateChange Oct 22, 2026
Merge latest dev while retaining the graph-state APIs and the legacy
measurement callback contract. Preserve regression coverage and correct
selector equality and detached-record cache behavior.
@samuelgja
samuelgja requested a balanced review from Copilot October 29, 2026 07:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Detached collections can retain stale snapshots after same-ID model replacements, and colliding array/string target keys can leave hooks using the wrong target or return shape without restarting watches.

2 open issues
3 resolved since last review

Review effort: Balanced

Comment thread packages/joint-react/src/hooks/use-cells.utils.ts Outdated
Comment thread packages/joint-react/src/store/graph-state.ts Outdated

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants