What happens
useOnElementsMeasured fires on every change:size, not only when the elements were measured. Any resize the application makes itself wakes every subscriber.
In our CI Pipeline Editor demo the layout sizes its group containers around their content. Collapsing a group produces one measurement callback although nothing was re-measured, and on load three callbacks arrive for two measurement passes. A layout that resizes cells therefore re-enters its own callback, and without a guard it runs twice per burst.
What is documented
packages/joint-react/src/hooks/use-on-elements-measured.ts:33, shipped verbatim in dist/types/index.d.ts:
Fires on the first measurement pass (at least one element has been sized) and again whenever an element is resized.
The resizing there is the outcome of a measurement: the content of an element changed, it was measured again, and it turned out to have a different size. That is the pipeline the hook is named after, and the payload's isInitial separates the first such pass from the later ones. A size the application writes itself is not a measurement, so waking the subscribers for it is a defect, not the documented behaviour.
Why it happens
The store's size listener drops the options of the change (packages/joint-react/src/store/graph-changes.ts):
controller.listenTo(graph, 'change:size', (cell: dia.Cell, newSize: dia.Size) => {
if (!onElementsSizeChange) return;
onElementsSizeChange(cell.id, newSize);
});
so GraphStore bumps measureState for application writes as well as for its own measurement writes.
The marker to tell them apart already exists. The ResizeObserver pipeline writes with it (packages/joint-react/src/store/graph-store.ts):
model.set(attributes, { [AUTO_SIZE_OPTION]: true });
and the comment right above it states the intent: "marks writes that originate from the ResizeObserver pipeline so change:size listeners can tell our own writes apart from external ones (controlled-mode sync, direct cell.resize, etc.) and avoid feedback loops." The dev-only warnAutoSizeResize listener already uses it.
Proposal
Pass the options through the change:size listener and bump measureState only for measurement writes.
Nothing is lost by narrowing it. Whoever wants every size change, whatever its origin, already has useGraphEvents({ 'change:size': ... }). useOnElementsMeasured is the only way to hear the measurement pipeline, and today it cannot be heard on its own.
Worth keeping separate: the bookkeeping of which elements have a non-zero size (which isInitial and useAreElementsMeasured rest on) should still follow every size change, including seeded and application-set sizes. Only the counter that wakes the subscribers needs the condition.
Workarounds today
- Compare a signature of the measured sizes in the callback and return early when nothing changed (what the demo does).
- Resize the layout-owned cells with
{ silent: true }, which skips change:size altogether. Only safe where nothing else needs the event.
Version
@joint/react 4.3.5, through @joint/react-plus 4.3.3.
What happens
useOnElementsMeasuredfires on everychange:size, not only when the elements were measured. Any resize the application makes itself wakes every subscriber.In our CI Pipeline Editor demo the layout sizes its group containers around their content. Collapsing a group produces one measurement callback although nothing was re-measured, and on load three callbacks arrive for two measurement passes. A layout that resizes cells therefore re-enters its own callback, and without a guard it runs twice per burst.
What is documented
packages/joint-react/src/hooks/use-on-elements-measured.ts:33, shipped verbatim indist/types/index.d.ts:The resizing there is the outcome of a measurement: the content of an element changed, it was measured again, and it turned out to have a different size. That is the pipeline the hook is named after, and the payload's
isInitialseparates the first such pass from the later ones. A size the application writes itself is not a measurement, so waking the subscribers for it is a defect, not the documented behaviour.Why it happens
The store's size listener drops the options of the change (
packages/joint-react/src/store/graph-changes.ts):so
GraphStorebumpsmeasureStatefor application writes as well as for its own measurement writes.The marker to tell them apart already exists. The ResizeObserver pipeline writes with it (
packages/joint-react/src/store/graph-store.ts):and the comment right above it states the intent: "marks writes that originate from the ResizeObserver pipeline so
change:sizelisteners can tell our own writes apart from external ones (controlled-mode sync, directcell.resize, etc.) and avoid feedback loops." The dev-onlywarnAutoSizeResizelistener already uses it.Proposal
Pass the options through the
change:sizelistener and bumpmeasureStateonly for measurement writes.Nothing is lost by narrowing it. Whoever wants every size change, whatever its origin, already has
useGraphEvents({ 'change:size': ... }).useOnElementsMeasuredis the only way to hear the measurement pipeline, and today it cannot be heard on its own.Worth keeping separate: the bookkeeping of which elements have a non-zero size (which
isInitialanduseAreElementsMeasuredrest on) should still follow every size change, including seeded and application-set sizes. Only the counter that wakes the subscribers needs the condition.Workarounds today
{ silent: true }, which skipschange:sizealtogether. Only safe where nothing else needs the event.Version
@joint/react4.3.5, through@joint/react-plus4.3.3.