Fix excessive noteContext calls (#8233)

This commit is contained in:
Elian Doran 2026-01-01 21:09:39 +02:00 committed by GitHub
commit b2c3d78773
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 9 deletions

View File

@ -57,6 +57,18 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
return this;
}
/**
* Removes a child component from this component's children array.
* This is used for cleanup when a widget is unmounted to prevent event listener accumulation.
*/
removeChild(component: ChildT) {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
component.parent = undefined;
}
}
handleEvent<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null | undefined {
try {
const callMethodPromise = this.initialized ? this.initialized.then(() => this.callMethod((this as any)[`${name}Event`], data)) : this.callMethod((this as any)[`${name}Event`], data);

View File

@ -634,7 +634,8 @@ export function useLegacyWidget<T extends BasicWidget>(widgetFactory: () => T, {
const ref = useRef<HTMLDivElement>(null);
const parentComponent = useContext(ParentComponent);
// Render the widget once.
// Render the widget once - note that noteContext is intentionally NOT a dependency
// to prevent creating new widget instances on every note switch.
const [ widget, renderedWidget ] = useMemo(() => {
const widget = widgetFactory();
@ -642,14 +643,21 @@ export function useLegacyWidget<T extends BasicWidget>(widgetFactory: () => T, {
parentComponent.child(widget);
}
if (noteContext && widget instanceof NoteContextAwareWidget) {
widget.setNoteContextEvent({ noteContext });
}
const renderedWidget = widget.render();
return [ widget, renderedWidget ];
}, [ noteContext, parentComponent ]); // eslint-disable-line react-hooks/exhaustive-deps
// widgetFactory() is intentionally left out
}, [ parentComponent ]); // eslint-disable-line react-hooks/exhaustive-deps
// widgetFactory() and noteContext are intentionally left out - widget should be created once
// and updated via activeContextChangedEvent when noteContext changes.
// Cleanup: remove widget from parent's children when unmounted
useEffect(() => {
return () => {
if (parentComponent) {
parentComponent.removeChild(widget);
}
widget.cleanup();
};
}, [ parentComponent, widget ]);
// Attach the widget to the parent.
useEffect(() => {
@ -660,10 +668,17 @@ export function useLegacyWidget<T extends BasicWidget>(widgetFactory: () => T, {
}
}, [ renderedWidget ]);
// Inject the note context.
// Inject the note context - this updates the existing widget without recreating it.
// We check if the context actually changed to avoid double refresh when the event system
// also delivers activeContextChanged to the widget through component tree propagation.
useEffect(() => {
if (noteContext && widget instanceof NoteContextAwareWidget) {
widget.activeContextChangedEvent({ noteContext });
// Only trigger refresh if the context actually changed.
// The event system may have already updated the widget, in which case
// widget.noteContext will already equal noteContext.
if (widget.noteContext !== noteContext) {
widget.activeContextChangedEvent({ noteContext });
}
}
}, [ noteContext, widget ]);