rusackas commented on PR #35777:
URL: https://github.com/apache/superset/pull/35777#issuecomment-3543374655

   OK, here's the big diff it provided. It claims this will do what I was 
mentioning, and
   • Keep chart localStorage persistence per chart
   • Clean up all chart legend state keys on SPA route navigation and browser 
close, in a single place
   • Reduce code complexity and improve testability
   
   ```
   *** Update File: superset-frontend/src/components/Chart/ChartRenderer.jsx
   @@
   -import ChartContextMenu from './ChartContextMenu/ChartContextMenu';
   +import ChartContextMenu from './ChartContextMenu/ChartContextMenu';
   +import { getItem, setItem } from 'src/utils/localStorageHelpers';
   @@
   -    this.state = {
   -      showContextMenu:
   -        props.source === ChartSource.Dashboard &&
   -        !suppressContextMenu &&
   -        isFeatureEnabled(FeatureFlag.DrillToDetail),
   -      inContextMenu: false,
   -      legendState: undefined,
   -      legendIndex: 0,
   -    };
   +    // Load per-chart legend persistence from localStorage (if present)
   +    const legendStateKey = `chart_legend_state_${props.chartId}`;
   +    const legendIndexKey = `chart_legend_index_${props.chartId}`;
   +    const savedLegendState = getItem(legendStateKey, undefined);
   +    const savedLegendIndex = getItem(legendIndexKey, 0);
   +
   +    this.state = {
   +      showContextMenu:
   +        props.source === ChartSource.Dashboard &&
   +        !suppressContextMenu &&
   +        isFeatureEnabled(FeatureFlag.DrillToDetail),
   +      inContextMenu: false,
   +      legendState: savedLegendState,
   +      legendIndex: savedLegendIndex,
   +    };
   @@
      handleLegendStateChanged(legendState) {
   -    this.setState({ legendState });
   +    this.setState({ legendState });
   +    try {
   +      setItem(`chart_legend_state_${this.props.chartId}`, legendState);
   +    } catch (e) {
   +      // swallow errors to avoid breaking charts if localStorage is 
unavailable
   +    }
      }
   @@
      handleLegendScroll(legendIndex) {
   -    this.setState({ legendIndex });
   +    this.setState({ legendIndex });
   +    try {
   +      setItem(`chart_legend_index_${this.props.chartId}`, legendIndex);
   +    } catch (e) {
   +      // swallow errors to avoid breaking charts if localStorage is 
unavailable
   +    }
      }
   *** End Patch
   
   *** Add File: superset-frontend/src/components/Chart/legendStateUtils.ts
   +export function cleanupAllChartLegendStates() {
   +  try {
   +    const keysToRemove = [];
   +    for (let i = 0; i < localStorage.length; i += 1) {
   +      const key = localStorage.key(i);
   +      if (
   +        key &&
   +        (key.startsWith('chart_legend_state_') || 
key.startsWith('chart_legend_index_'))
   +      ) {
   +        keysToRemove.push(key);
   +      }
   +    }
   +    keysToRemove.forEach(k => localStorage.removeItem(k));
   +  } catch (e) {
   +    // intentionally no-op; callers shouldn't fail app when cleanup can't 
run
   +  }
   +}
   *** End Patch
   
   *** Add File: superset-frontend/src/components/Chart/RouteLegendCleanup.tsx
   +import React, { useEffect, useRef } from 'react';
   +import { useLocation } from 'react-router-dom';
   +import { cleanupAllChartLegendStates } from './legendStateUtils';
   +
   +export default function RouteLegendCleanup() {
   +  const location = useLocation();
   +  const prevPath = useRef(location.pathname);
   +
   +  useEffect(() => {
   +    if (prevPath.current !== location.pathname) {
   +      cleanupAllChartLegendStates();
   +      prevPath.current = location.pathname;
   +    }
   +  }, [location.pathname]);
   +
   +  useEffect(() => {
   +    const onUnload = () => cleanupAllChartLegendStates();
   +    window.addEventListener('beforeunload', onUnload);
   +    return () => window.removeEventListener('beforeunload', onUnload);
   +  }, []);
   +
   +  return null;
   +}
   *** End Patch
   
   *** Update File: superset-frontend/src/views/App.tsx
   @@
   -import { ScrollToTop } from './ScrollToTop';
   +import { ScrollToTop } from './ScrollToTop';
   +import RouteLegendCleanup from 'src/components/Chart/RouteLegendCleanup';
   @@
        <ScrollToTop />
        <LocationPathnameLogger />
   +    <RouteLegendCleanup />
   *** End Patch
   ```
   
   Summary
   This refactor centralizes all chart legend localStorage cleanup on SPA 
navigation and browser exit, removes per-component global listeners/polling, 
and keeps ChartRenderer focused on per-chart persistence.
   It adds a new router-level cleanup component, making legend state cleanup 
more maintainable, efficient, and easier to test.
   
   What's changed:
   
   ChartRenderer.jsx now only loads/saves legend state/index for each chart to 
localStorage
   Removes all global listeners, polling, and window globals from ChartRenderer
   Adds RouteLegendCleanup component (using react-router v5's useLocation) that 
triggers cleanup when route changes and on browser unload
   Cleanup helper (legendStateUtils.ts) is now used by both the router watcher 
and tests
   Mounts RouteLegendCleanup once inside Router in App.tsx (after 
LocationPathnameLogger)
   See patch for all unified changes
   Why this is better:
   
   No polling, no multiple global listeners
   Cleanup runs only once on navigation and browser unload
   Per-chart persistence is simple (no more component tracking/counting)
   Easier to test (single cleanup, focused responsibilities, less time-based 
logic)
   Testing:
   
   ChartRenderer persists/loads legend state/index to localStorage as before
   RouteLegendCleanup runs cleanup only when navigating between pages 
(pathnames) and once on browser unload
   You can unit test RouteLegendCleanup with a MemoryRouter and assert cleanup 
calls
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to