diff --git a/.betterer.results b/.betterer.results index f2b87c11102..c3a505eef84 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5726,11 +5726,9 @@ exports[`better eslint`] = { "public/app/features/scenes/components/SceneFlexLayout.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/scenes/core/SceneComponentEditWrapper.tsx:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"] + "public/app/features/scenes/core/SceneComponentWrapper.tsx:5381": [ + [0, 0, 0, "Do not use any type assertions.", "0"], + [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], "public/app/features/scenes/core/SceneObjectBase.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/public/app/features/scenes/components/Scene.tsx b/public/app/features/scenes/components/Scene.tsx index 7bf37c758b5..a5e52ad0612 100644 --- a/public/app/features/scenes/components/Scene.tsx +++ b/public/app/features/scenes/components/Scene.tsx @@ -17,13 +17,13 @@ export class Scene extends SceneObjectBase { static Component = SceneRenderer; urlSyncManager?: UrlSyncManager; - onMount() { - super.onMount(); + activate() { + super.activate(); this.urlSyncManager = new UrlSyncManager(this); } - onUnmount() { - super.onUnmount(); + deactivate() { + super.deactivate(); this.urlSyncManager!.cleanUp(); } } @@ -31,8 +31,6 @@ export class Scene extends SceneObjectBase { function SceneRenderer({ model }: SceneComponentProps) { const { title, layout, actions = [], isEditing, $editor } = model.useState(); - console.log('render scene'); - return (
diff --git a/public/app/features/scenes/components/ScenePanelRepeater.tsx b/public/app/features/scenes/components/ScenePanelRepeater.tsx index 6d2ba7569e6..81c3d3441e4 100644 --- a/public/app/features/scenes/components/ScenePanelRepeater.tsx +++ b/public/app/features/scenes/components/ScenePanelRepeater.tsx @@ -11,8 +11,8 @@ interface RepeatOptions extends SceneObjectState { } export class ScenePanelRepeater extends SceneObjectBase { - onMount() { - super.onMount(); + activate(): void { + super.activate(); this.subs.add( this.getData().subscribe({ diff --git a/public/app/features/scenes/core/SceneComponentWrapper.tsx b/public/app/features/scenes/core/SceneComponentWrapper.tsx new file mode 100644 index 00000000000..74cfb834d6b --- /dev/null +++ b/public/app/features/scenes/core/SceneComponentWrapper.tsx @@ -0,0 +1,32 @@ +import React, { useEffect } from 'react'; + +import { SceneComponentEditingWrapper } from '../editor/SceneComponentEditWrapper'; + +import { SceneComponentProps, SceneObject } from './types'; + +export function SceneComponentWrapper({ model, isEditing }: SceneComponentProps) { + const Component = (model as any).constructor['Component'] ?? EmptyRenderer; + const inner = ; + + // Handle component activation state state + useEffect(() => { + if (!model.isActive) { + model.activate(); + } + return () => { + if (model.isActive) { + model.deactivate(); + } + }; + }, [model]); + + if (!isEditing) { + return inner; + } + + return {inner}; +} + +function EmptyRenderer(_: SceneComponentProps): React.ReactElement | null { + return null; +} diff --git a/public/app/features/scenes/core/SceneObjectBase.test.ts b/public/app/features/scenes/core/SceneObjectBase.test.ts index 2ac12132c81..2cfc82c73f1 100644 --- a/public/app/features/scenes/core/SceneObjectBase.test.ts +++ b/public/app/features/scenes/core/SceneObjectBase.test.ts @@ -23,12 +23,12 @@ describe('SceneObject', () => { ], }); - scene.state.nested?.onMount(); + scene.state.nested?.activate(); const clone = scene.clone(); expect(clone).not.toBe(scene); expect(clone.state.nested).not.toBe(scene.state.nested); - expect(clone.state.nested?.isMounted).toBe(undefined); + expect(clone.state.nested?.isActive).toBe(undefined); expect(clone.state.children![0]).not.toBe(scene.state.children![0]); }); diff --git a/public/app/features/scenes/core/SceneObjectBase.tsx b/public/app/features/scenes/core/SceneObjectBase.tsx index 89c08b11bdd..56eca2e0b0e 100644 --- a/public/app/features/scenes/core/SceneObjectBase.tsx +++ b/public/app/features/scenes/core/SceneObjectBase.tsx @@ -1,11 +1,10 @@ -import { useEffect } from 'react'; import { useObservable } from 'react-use'; import { Observer, Subject, Subscription } from 'rxjs'; import { v4 as uuidv4 } from 'uuid'; import { EventBusSrv } from '@grafana/data'; -import { SceneComponentEditWrapper } from './SceneComponentEditWrapper'; +import { SceneComponentWrapper } from './SceneComponentWrapper'; import { SceneObjectStateChangedEvent } from './events'; import { SceneDataState, @@ -23,7 +22,7 @@ export abstract class SceneObjectBase impl state: TState; parent?: SceneObjectBase; subs = new Subscription(); - isMounted?: boolean; + isActive?: boolean; events = new EventBusSrv(); constructor(state: TState) { @@ -41,7 +40,7 @@ export abstract class SceneObjectBase impl * Wraps the component in an EditWrapper that handles edit mode */ get Component(): SceneComponent { - return SceneComponentEditWrapper; + return SceneComponentWrapper; } /** @@ -96,46 +95,27 @@ export abstract class SceneObjectBase impl return !this.parent ? this : this.parent.getRoot(); } - onMount() { - this.isMounted = true; + activate() { + this.isActive = true; const { $data } = this.state; - if ($data && !$data.isMounted) { - $data.onMount(); + if ($data && !$data.isActive) { + $data.activate(); } } - onUnmount() { - this.isMounted = false; + deactivate(): void { + this.isActive = false; const { $data } = this.state; - if ($data && $data.isMounted) { - $data.onUnmount(); + if ($data && $data.isActive) { + $data.deactivate(); } this.subs.unsubscribe(); this.subs = new Subscription(); } - /** - * The scene object needs to know when the react component is mounted to trigger query and other lazy actions - */ - useMount() { - // eslint-disable-next-line react-hooks/rules-of-hooks - useEffect(() => { - if (!this.isMounted) { - this.onMount(); - } - return () => { - if (this.isMounted) { - this.onUnmount(); - } - }; - }, []); - - return this; - } - useState() { // eslint-disable-next-line react-hooks/rules-of-hooks return useObservable(this.subject, this.state); diff --git a/public/app/features/scenes/core/types.ts b/public/app/features/scenes/core/types.ts index f44bfee7b21..bb32a56b38f 100644 --- a/public/app/features/scenes/core/types.ts +++ b/public/app/features/scenes/core/types.ts @@ -41,7 +41,7 @@ export interface SceneObject state: TState; /** True when there is a React component mounted for this Object */ - isMounted?: boolean; + isActive?: boolean; /** SceneObject parent */ parent?: SceneObject; @@ -55,14 +55,11 @@ export interface SceneObject /** How to modify state */ setState(state: Partial): void; - /** Utility hook for main component so that object knows when it's mounted */ - useMount(): this; - - /** Called when component mounts. A place to register event listeners add subscribe to state changes */ - onMount(): void; + /** Called when the Component is mounted. A place to register event listeners add subscribe to state changes */ + activate(): void; /** Called when component unmounts. Unsubscribe to events */ - onUnmount(): void; + deactivate(): void; /** Get the scene editor */ getSceneEditor(): SceneEditor; diff --git a/public/app/features/scenes/core/SceneComponentEditWrapper.tsx b/public/app/features/scenes/editor/SceneComponentEditWrapper.tsx similarity index 69% rename from public/app/features/scenes/core/SceneComponentEditWrapper.tsx rename to public/app/features/scenes/editor/SceneComponentEditWrapper.tsx index 9bbde1c2511..5049e06f893 100644 --- a/public/app/features/scenes/core/SceneComponentEditWrapper.tsx +++ b/public/app/features/scenes/editor/SceneComponentEditWrapper.tsx @@ -4,26 +4,9 @@ import React, { CSSProperties } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { SceneObjectBase } from './SceneObjectBase'; -import { SceneComponentProps } from './types'; +import { SceneObject } from '../core/types'; -export function SceneComponentEditWrapper>({ - model, - isEditing, -}: SceneComponentProps) { - const Component = (model as any).constructor['Component'] ?? EmptyRenderer; - const inner = ; - - model.useMount(); - - if (!isEditing) { - return inner; - } - - return {inner}; -} - -export function SceneComponentEditingWrapper>({ +export function SceneComponentEditingWrapper({ model, children, }: { @@ -76,7 +59,3 @@ const getStyles = (theme: GrafanaTheme2) => { }), }; }; - -function EmptyRenderer(_: SceneComponentProps): React.ReactElement | null { - return null; -} diff --git a/public/app/features/scenes/querying/SceneQueryRunner.ts b/public/app/features/scenes/querying/SceneQueryRunner.ts index fb76b1c5088..3d006885d3b 100644 --- a/public/app/features/scenes/querying/SceneQueryRunner.ts +++ b/public/app/features/scenes/querying/SceneQueryRunner.ts @@ -31,8 +31,8 @@ export interface DataQueryExtended extends DataQuery { export class SceneQueryRunner extends SceneObjectBase { private querySub?: Unsubscribable; - onMount() { - super.onMount(); + activate() { + super.activate(); const timeRange = this.getTimeRange(); @@ -49,12 +49,9 @@ export class SceneQueryRunner extends SceneObjectBase { } } - onUnmount() { - super.onUnmount(); - this.cleanUp(); - } + deactivate(): void { + super.deactivate(); - cleanUp() { if (this.querySub) { this.querySub.unsubscribe(); this.querySub = undefined; @@ -108,8 +105,6 @@ export class SceneQueryRunner extends SceneObjectBase { request.interval = norm.interval; request.intervalMs = norm.intervalMs; - console.log('Query runner run'); - this.querySub = runRequest(ds, request).subscribe({ next: (data) => { console.log('set data', data, data.state);