From 52dbd11805f7629c6a78543399b875a346418113 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Wed, 11 Mar 2026 22:58:51 -0500 Subject: [PATCH 1/4] try something --- .../components/VizPanel/VizPanelRenderer.tsx | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index ac2028082..8c7445f23 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -1,6 +1,6 @@ import { Trans } from '@grafana/i18n'; -import React, { RefCallback, useCallback, useEffect, useLayoutEffect, useMemo } from 'react'; -import { useMeasure } from 'react-use'; +import React, { RefCallback, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'; +import { useMeasure, usePrevious } from 'react-use'; // @ts-ignore import { AlertState, GrafanaTheme2, PanelData, PluginContextProvider, SetPanelAttentionEvent } from '@grafana/data'; @@ -85,6 +85,9 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { const dataObject = sceneGraph.getData(model); const rawData = dataObject.useState(); + + useClearPreviousData(rawData.data); + const dataWithSeriesLimit = useDataWithSeriesLimit(rawData.data, seriesLimit, seriesLimitShowAll); const dataWithFieldConfig = model.applyFieldConfig(dataWithSeriesLimit); const sceneTimeRange = sceneGraph.getTimeRange(model); @@ -305,6 +308,46 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { ); } +function useClearPreviousData(data?: PanelData) { + // this holds all value arrays from all series and anno frames + // so we can empty any previous ones that no loger appear in current data + // why? because React fiber: https://github.com/facebook/react/issues/14380 + const prevVals = useRef>(); + const currVals = useRef>(); + prevVals.current ??= new Set(); + currVals.current ??= new Set(); + + const currSeries = data?.series; + const prevSeries = usePrevious(currSeries); + + if (currSeries != null && currSeries !== prevSeries) { + // populate new + currVals.current.clear(); + + for (let i = 0; i < currSeries.length; i++) { + let fields = currSeries[i].fields; + + for (let i = 0; i < fields.length; i++) { + currVals.current.add(fields[i].values); + } + } + + // empty out all prev not seen in new + // prevVals.current.difference(currVals.current); + prevVals.current.forEach((vals) => { + if (!currVals.current!.has(vals)) { + vals.length = 0; + } + }); + prevVals.current.clear(); + prevVals.current = new Set(currVals.current); + + // prevSeries.length = 0; + } + + // const prevAnnos = usePrevious(data.annotations); +} + function useDataWithSeriesLimit(data: PanelData | undefined, seriesLimit?: number, showAllSeries?: boolean) { return useMemo(() => { if (!data?.series || !seriesLimit || showAllSeries) { From c4a05d76e2cf6447dbb2ff36fc6018d32586a08f Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Wed, 1 Apr 2026 20:10:31 -0500 Subject: [PATCH 2/4] add feature flag guard --- .../components/VizPanel/VizPanelRenderer.tsx | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index 8c7445f23..361433cd6 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -3,9 +3,16 @@ import React, { RefCallback, useCallback, useEffect, useLayoutEffect, useMemo, u import { useMeasure, usePrevious } from 'react-use'; // @ts-ignore -import { AlertState, GrafanaTheme2, PanelData, PluginContextProvider, SetPanelAttentionEvent } from '@grafana/data'; - -import { getAppEvents } from '@grafana/runtime'; +import { + AlertState, + DataFrame, + GrafanaTheme2, + PanelData, + PluginContextProvider, + SetPanelAttentionEvent, +} from '@grafana/data'; + +import { config, getAppEvents } from '@grafana/runtime'; import { PanelChrome, ErrorBoundaryAlert, PanelContextProvider, Tooltip, useStyles2, Icon } from '@grafana/ui'; import { sceneGraph } from '../../core/sceneGraph'; @@ -86,7 +93,13 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { const rawData = dataObject.useState(); - useClearPreviousData(rawData.data); + // @ts-ignore + const clearPreviousFieldValues = Boolean(config.featureToggles.clearPreviousFieldValues); + + const { series, annotations } = clearPreviousFieldValues ? rawData.data ?? {} : {}; + + useClearPreviousData(series); + useClearPreviousData(annotations); const dataWithSeriesLimit = useDataWithSeriesLimit(rawData.data, seriesLimit, seriesLimitShowAll); const dataWithFieldConfig = model.applyFieldConfig(dataWithSeriesLimit); @@ -308,24 +321,24 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { ); } -function useClearPreviousData(data?: PanelData) { - // this holds all value arrays from all series and anno frames +function useClearPreviousData(data?: DataFrame[]) { + // this holds all value arrays from all series or anno frames // so we can empty any previous ones that no loger appear in current data - // why? because React fiber: https://github.com/facebook/react/issues/14380 + // why? because React fiber: https://github.com/facebook/react/issues/36176 const prevVals = useRef>(); const currVals = useRef>(); prevVals.current ??= new Set(); currVals.current ??= new Set(); - const currSeries = data?.series; - const prevSeries = usePrevious(currSeries); + const currFrames = data; + const prevFrames = usePrevious(currFrames); - if (currSeries != null && currSeries !== prevSeries) { + if (currFrames != null && currFrames !== prevFrames) { // populate new currVals.current.clear(); - for (let i = 0; i < currSeries.length; i++) { - let fields = currSeries[i].fields; + for (let i = 0; i < currFrames.length; i++) { + let fields = currFrames[i].fields; for (let i = 0; i < fields.length; i++) { currVals.current.add(fields[i].values); @@ -333,7 +346,6 @@ function useClearPreviousData(data?: PanelData) { } // empty out all prev not seen in new - // prevVals.current.difference(currVals.current); prevVals.current.forEach((vals) => { if (!currVals.current!.has(vals)) { vals.length = 0; @@ -341,11 +353,7 @@ function useClearPreviousData(data?: PanelData) { }); prevVals.current.clear(); prevVals.current = new Set(currVals.current); - - // prevSeries.length = 0; } - - // const prevAnnos = usePrevious(data.annotations); } function useDataWithSeriesLimit(data: PanelData | undefined, seriesLimit?: number, showAllSeries?: boolean) { From f6cd520090724b1c312fcc7d10e010810529d564 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 2 Apr 2026 09:12:48 -0500 Subject: [PATCH 3/4] use VizPanelState instead of FF --- packages/scenes/src/components/VizPanel/VizPanel.tsx | 12 ++++++++++++ .../src/components/VizPanel/VizPanelRenderer.tsx | 9 +++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanel.tsx b/packages/scenes/src/components/VizPanel/VizPanel.tsx index 12b1bb044..ce2ecfc13 100644 --- a/packages/scenes/src/components/VizPanel/VizPanel.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanel.tsx @@ -91,6 +91,18 @@ export interface VizPanelState extends SceneOb */ extendPanelContext?: (vizPanel: VizPanel, context: PanelContext) => void; + /** + * @internal + * experimental / temporary + * + * clears field.values arrays of previous/stale/retained series and annotations frames in PanelData + * + * see https://github.com/facebook/react/issues/36176 + * see https://github.com/grafana/grafana/pull/121682 + * see https://github.com/grafana/grafana/pull/120190 + **/ + _UNSAFE_clearPreviousFieldValues?: boolean; + /** * Sets panel chrome collapsed state */ diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index 361433cd6..f47ed4f5e 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -12,7 +12,7 @@ import { SetPanelAttentionEvent, } from '@grafana/data'; -import { config, getAppEvents } from '@grafana/runtime'; +import { getAppEvents } from '@grafana/runtime'; import { PanelChrome, ErrorBoundaryAlert, PanelContextProvider, Tooltip, useStyles2, Icon } from '@grafana/ui'; import { sceneGraph } from '../../core/sceneGraph'; @@ -44,6 +44,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { collapsible, collapsed, _renderCounter = 0, + _UNSAFE_clearPreviousFieldValues = false, } = model.useState(); const [ref, { width, height }] = useMeasure(); const appEvents = useMemo(() => getAppEvents(), []); @@ -93,11 +94,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { const rawData = dataObject.useState(); - // @ts-ignore - const clearPreviousFieldValues = Boolean(config.featureToggles.clearPreviousFieldValues); - - const { series, annotations } = clearPreviousFieldValues ? rawData.data ?? {} : {}; - + const { series, annotations } = _UNSAFE_clearPreviousFieldValues ? rawData.data ?? {} : {}; useClearPreviousData(series); useClearPreviousData(annotations); From 2cdc7ebc587a44c749fc4d4012c1edf78198b1db Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 2 Apr 2026 09:14:12 -0500 Subject: [PATCH 4/4] j --- .../scenes/src/components/VizPanel/VizPanelRenderer.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index f47ed4f5e..7bb768585 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -320,7 +320,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { function useClearPreviousData(data?: DataFrame[]) { // this holds all value arrays from all series or anno frames - // so we can empty any previous ones that no loger appear in current data + // so we can empty any previous ones that no longer appear in current data // why? because React fiber: https://github.com/facebook/react/issues/36176 const prevVals = useRef>(); const currVals = useRef>(); @@ -337,8 +337,8 @@ function useClearPreviousData(data?: DataFrame[]) { for (let i = 0; i < currFrames.length; i++) { let fields = currFrames[i].fields; - for (let i = 0; i < fields.length; i++) { - currVals.current.add(fields[i].values); + for (let j = 0; j < fields.length; j++) { + currVals.current.add(fields[j].values); } }