This is an automated email from the ASF dual-hosted git repository.

kgabryje pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new ce0e06a935 perf: Optimize native filters and cross filters (#31243)
ce0e06a935 is described below

commit ce0e06a9359feaf2181e33f71e42284058a09c35
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Mon Dec 2 15:42:34 2024 +0100

    perf: Optimize native filters and cross filters (#31243)
---
 .../DashboardBuilder/DashboardContainer.tsx        |   9 +-
 .../dashboard/components/FiltersBadge/index.tsx    |  17 +-
 .../ScopingModal/ScopingModal.test.tsx             |   3 +
 .../CrossFilters/ScopingModal/ScopingModal.tsx     |  24 ++-
 .../FilterBar/CrossFilters/Vertical.tsx            |  18 +-
 .../FilterBar/CrossFilters/selectors.ts            |  27 +--
 .../FilterBar/FilterControls/FilterControls.tsx    | 201 +++++++++++----------
 .../FilterBar/FilterControls/FilterValue.tsx       |   2 +-
 .../nativeFilters/FilterBar/Horizontal.tsx         |  38 ++--
 .../components/nativeFilters/FilterBar/index.tsx   |  48 +++--
 .../nativeFilters/FilterBar/useFilterOutlined.ts   |  25 ++-
 .../components/nativeFilters/FilterBar/utils.ts    |  31 ++--
 .../nativeFilters/FilterCard/FilterCard.test.tsx   |   3 +
 .../components/nativeFilters/selectors.ts          |  32 ++--
 .../dashboard/components/nativeFilters/state.ts    |  55 +++---
 .../components/nativeFilters/utils.test.ts         |   6 +-
 .../dashboard/components/nativeFilters/utils.ts    |  93 ++--------
 .../src/dashboard/util/charts/useChartIds.ts       |  17 +-
 .../src/dashboard/util/crossFilters.ts             |  11 +-
 .../src/dashboard/util/getChartIdsInFilterScope.ts |   5 +-
 .../useChartIds.ts => useChartLayoutItems.ts}      |  21 +--
 .../util/useFilterFocusHighlightStyles.test.tsx    |  50 -----
 .../util/useFilterFocusHighlightStyles.ts          |  64 ++-----
 23 files changed, 354 insertions(+), 446 deletions(-)

diff --git 
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
 
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
index c333e5318a..929c5af8fd 100644
--- 
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
+++ 
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
@@ -60,6 +60,7 @@ import {
   ensureSyncedSharedLabelsColors,
   ensureSyncedLabelsColorMap,
 } from 'src/dashboard/actions/dashboardState';
+import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
 import { getColorNamespace, resetColors } from 'src/utils/colorScheme';
 import { NATIVE_FILTER_DIVIDER_PREFIX } from 
'../nativeFilters/FiltersConfigModal/utils';
 import { findTabsWithChartsInScope } from '../nativeFilters/utils';
@@ -160,14 +161,18 @@ const DashboardContainer: FC<DashboardContainerProps> = 
({ topLevelTabs }) => {
         };
       }
 
+      const chartLayoutItems = Object.values(dashboardLayout).filter(
+        item => item?.type === CHART_TYPE,
+      );
+
       const chartsInScope: number[] = getChartIdsInFilterScope(
         filterScope.scope,
         chartIds,
-        dashboardLayout,
+        chartLayoutItems,
       );
 
       const tabsInScope = findTabsWithChartsInScope(
-        dashboardLayout,
+        chartLayoutItems,
         chartsInScope,
       );
       return {
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx 
b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
index 485879e959..57413ffd23 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
@@ -39,6 +39,7 @@ import {
 } from '@superset-ui/core';
 import Icons from 'src/components/Icons';
 import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
 import Badge from 'src/components/Badge';
 import DetailsPanelPopover from './DetailsPanel';
 import {
@@ -47,7 +48,7 @@ import {
   selectIndicatorsForChart,
   selectNativeIndicatorsForChart,
 } from '../nativeFilters/selectors';
-import { Chart, DashboardLayout, RootState } from '../../types';
+import { Chart, RootState } from '../../types';
 
 export interface FiltersBadgeProps {
   chartId: number;
@@ -126,9 +127,7 @@ export const FiltersBadge = ({ chartId }: 
FiltersBadgeProps) => {
     state => state.dashboardInfo.metadata?.chart_configuration,
   );
   const chart = useSelector<RootState, Chart>(state => state.charts[chartId]);
-  const present = useSelector<RootState, DashboardLayout>(
-    state => state.dashboardLayout.present,
-  );
+  const chartLayoutItems = useChartLayoutItems();
   const dataMask = useSelector<RootState, DataMaskStateWithId>(
     state => state.dataMask,
   );
@@ -207,7 +206,7 @@ export const FiltersBadge = ({ chartId }: 
FiltersBadgeProps) => {
   ]);
 
   const prevNativeFilters = usePrevious(nativeFilters);
-  const prevDashboardLayout = usePrevious(present);
+  const prevChartLayoutItems = usePrevious(chartLayoutItems);
   const prevDataMask = usePrevious(dataMask);
   const prevChartConfig = usePrevious(chartConfiguration);
 
@@ -221,7 +220,7 @@ export const FiltersBadge = ({ chartId }: 
FiltersBadgeProps) => {
         chart?.queriesResponse?.[0]?.applied_filters !==
           prevChart?.queriesResponse?.[0]?.applied_filters ||
         nativeFilters !== prevNativeFilters ||
-        present !== prevDashboardLayout ||
+        chartLayoutItems !== prevChartLayoutItems ||
         dataMask !== prevDataMask ||
         prevChartConfig !== chartConfiguration
       ) {
@@ -231,7 +230,7 @@ export const FiltersBadge = ({ chartId }: 
FiltersBadgeProps) => {
             dataMask,
             chartId,
             chart,
-            present,
+            chartLayoutItems,
             chartConfiguration,
           ),
         );
@@ -244,14 +243,14 @@ export const FiltersBadge = ({ chartId }: 
FiltersBadgeProps) => {
     dataMask,
     nativeFilters,
     nativeIndicators.length,
-    present,
     prevChart?.queriesResponse,
     prevChartConfig,
     prevChartStatus,
-    prevDashboardLayout,
     prevDataMask,
     prevNativeFilters,
     showIndicators,
+    chartLayoutItems,
+    prevChartLayoutItems,
   ]);
 
   const indicators = useMemo(
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
index 392db3ae09..8914398c54 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
@@ -39,6 +39,9 @@ const INITIAL_STATE = {
     3: { id: 3 },
     4: { id: 4 },
   },
+  dashboardState: {
+    sliceIds: [1, 2, 3, 4],
+  },
   dashboardInfo: {
     id: 1,
     metadata: {
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx
index 61375f9ceb..e4530eacec 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx
@@ -22,7 +22,6 @@ import { isDefined, NativeFilterScope, t } from 
'@superset-ui/core';
 import Modal from 'src/components/Modal';
 import {
   ChartConfiguration,
-  Layout,
   RootState,
   isCrossFilterScopeGlobal,
   GlobalChartCrossFilterConfig,
@@ -32,6 +31,7 @@ import { getChartIdsInFilterScope } from 
'src/dashboard/util/getChartIdsInFilter
 import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
 import { saveChartConfiguration } from 'src/dashboard/actions/dashboardInfo';
 import { DEFAULT_CROSS_FILTER_SCOPING } from 'src/dashboard/constants';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
 import { ScopingModalContent } from './ScopingModalContent';
 import { NEW_CHART_SCOPING_ID } from './constants';
 
@@ -76,9 +76,7 @@ export const ScopingModal = ({
   closeModal,
 }: ScopingModalProps) => {
   const dispatch = useDispatch();
-  const layout = useSelector<RootState, Layout>(
-    state => state.dashboardLayout.present,
-  );
+  const chartLayoutItems = useChartLayoutItems();
   const chartIds = useChartIds();
   const [currentChartId, setCurrentChartId] = useState(initialChartId);
   const initialChartConfig = useSelector<RootState, ChartConfiguration>(
@@ -154,7 +152,11 @@ export const ScopingModal = ({
             id: currentChartId,
             crossFilters: {
               scope,
-              chartsInScope: getChartIdsInFilterScope(scope, chartIds, layout),
+              chartsInScope: getChartIdsInFilterScope(
+                scope,
+                chartIds,
+                chartLayoutItems,
+              ),
             },
           },
         }));
@@ -162,7 +164,7 @@ export const ScopingModal = ({
         const globalChartsInScope = getChartIdsInFilterScope(
           scope,
           chartIds,
-          layout,
+          chartLayoutItems,
         );
         setGlobalChartConfig({
           scope,
@@ -176,7 +178,7 @@ export const ScopingModal = ({
         );
       }
     },
-    [currentChartId, chartIds, layout],
+    [currentChartId, chartIds, chartLayoutItems],
   );
 
   const removeCustomScope = useCallback(
@@ -241,7 +243,11 @@ export const ScopingModal = ({
           id: newChartId,
           crossFilters: {
             scope: newScope,
-            chartsInScope: getChartIdsInFilterScope(newScope, chartIds, 
layout),
+            chartsInScope: getChartIdsInFilterScope(
+              newScope,
+              chartIds,
+              chartLayoutItems,
+            ),
           },
         };
 
@@ -275,7 +281,7 @@ export const ScopingModal = ({
       currentChartId,
       globalChartConfig.chartsInScope,
       globalChartConfig.scope,
-      layout,
+      chartLayoutItems,
     ],
   );
 
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
index 2064d0814f..5deff0a988 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
@@ -17,9 +17,11 @@
  * under the License.
  */
 
-import { DataMaskStateWithId, JsonObject } from '@superset-ui/core';
+import { DataMaskStateWithId } from '@superset-ui/core';
 import { useSelector } from 'react-redux';
-import { DashboardLayout, RootState } from 'src/dashboard/types';
+import { RootState } from 'src/dashboard/types';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
+import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
 import crossFiltersSelector from './selectors';
 import VerticalCollapse from './VerticalCollapse';
 import { useChartsVerboseMaps } from '../utils';
@@ -28,17 +30,13 @@ const CrossFiltersVertical = () => {
   const dataMask = useSelector<RootState, DataMaskStateWithId>(
     state => state.dataMask,
   );
-  const chartConfiguration = useSelector<RootState, JsonObject>(
-    state => state.dashboardInfo.metadata?.chart_configuration,
-  );
-  const dashboardLayout = useSelector<RootState, DashboardLayout>(
-    state => state.dashboardLayout.present,
-  );
+  const chartIds = useChartIds();
+  const chartLayoutItems = useChartLayoutItems();
   const verboseMaps = useChartsVerboseMaps();
   const selectedCrossFilters = crossFiltersSelector({
     dataMask,
-    chartConfiguration,
-    dashboardLayout,
+    chartIds,
+    chartLayoutItems,
     verboseMaps,
   });
 
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
index c8fd8e2841..2c2b3ec753 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
@@ -21,36 +21,37 @@ import {
   DataMaskStateWithId,
   getColumnLabel,
   isDefined,
-  JsonObject,
 } from '@superset-ui/core';
-import { DashboardLayout } from 'src/dashboard/types';
+import { LayoutItem } from 'src/dashboard/types';
 import { CrossFilterIndicator, getCrossFilterIndicator } from 
'../../selectors';
 
 export const crossFiltersSelector = (props: {
   dataMask: DataMaskStateWithId;
-  chartConfiguration: JsonObject;
-  dashboardLayout: DashboardLayout;
+  chartIds: number[];
+  chartLayoutItems: LayoutItem[];
   verboseMaps: { [key: string]: Record<string, string> };
 }): CrossFilterIndicator[] => {
-  const { dataMask, chartConfiguration, dashboardLayout, verboseMaps } = props;
-  const chartsIds = Object.keys(chartConfiguration || {});
+  const { dataMask, chartIds, chartLayoutItems, verboseMaps } = props;
 
-  return chartsIds
+  return chartIds
     .map(chartId => {
-      const id = Number(chartId);
       const filterIndicator = getCrossFilterIndicator(
-        id,
-        dataMask[id],
-        dashboardLayout,
+        chartId,
+        dataMask[chartId],
+        chartLayoutItems,
       );
       if (
         isDefined(filterIndicator.column) &&
         isDefined(filterIndicator.value)
       ) {
         const verboseColName =
-          verboseMaps[id]?.[getColumnLabel(filterIndicator.column)] ||
+          verboseMaps[chartId]?.[getColumnLabel(filterIndicator.column)] ||
           filterIndicator.column;
-        return { ...filterIndicator, column: verboseColName, emitterId: id };
+        return {
+          ...filterIndicator,
+          column: verboseColName,
+          emitterId: chartId,
+        };
       }
       return null;
     })
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
index 09415047b1..d959026792 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
@@ -37,7 +37,6 @@ import {
   isFeatureEnabled,
   FeatureFlag,
   isNativeFilterWithDataMask,
-  JsonObject,
 } from '@superset-ui/core';
 import {
   createHtmlPortalNode,
@@ -49,15 +48,13 @@ import {
   useDashboardHasTabs,
   useSelectFiltersInScope,
 } from 'src/dashboard/components/nativeFilters/state';
-import {
-  DashboardLayout,
-  FilterBarOrientation,
-  RootState,
-} from 'src/dashboard/types';
+import { FilterBarOrientation, RootState } from 'src/dashboard/types';
 import DropdownContainer, {
   Ref as DropdownContainerRef,
 } from 'src/components/DropdownContainer';
 import Icons from 'src/components/Icons';
+import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
 import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible';
 import { useFilterControlFactory } from '../useFilterControlFactory';
 import { FiltersDropdownContent } from '../FiltersDropdownContent';
@@ -65,12 +62,15 @@ import crossFiltersSelector from 
'../CrossFilters/selectors';
 import CrossFilter from '../CrossFilters/CrossFilter';
 import { useFilterOutlined } from '../useFilterOutlined';
 import { useChartsVerboseMaps } from '../utils';
+import { CrossFilterIndicator } from '../../selectors';
 
 type FilterControlsProps = {
   dataMaskSelected: DataMaskStateWithId;
   onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void;
 };
 
+const EMPTY_ARRAY: CrossFilterIndicator[] = [];
+
 const FilterControls: FC<FilterControlsProps> = ({
   dataMaskSelected,
   onFilterSelectionChange,
@@ -90,12 +90,8 @@ const FilterControls: FC<FilterControlsProps> = ({
   const dataMask = useSelector<RootState, DataMaskStateWithId>(
     state => state.dataMask,
   );
-  const chartConfiguration = useSelector<RootState, JsonObject>(
-    state => state.dashboardInfo.metadata?.chart_configuration,
-  );
-  const dashboardLayout = useSelector<RootState, DashboardLayout>(
-    state => state.dashboardLayout.present,
-  );
+  const chartIds = useChartIds();
+  const chartLayoutItems = useChartLayoutItems();
   const verboseMaps = useChartsVerboseMaps();
 
   const isCrossFiltersEnabled = isFeatureEnabled(
@@ -106,12 +102,12 @@ const FilterControls: FC<FilterControlsProps> = ({
       isCrossFiltersEnabled
         ? crossFiltersSelector({
             dataMask,
-            chartConfiguration,
-            dashboardLayout,
+            chartIds,
+            chartLayoutItems,
             verboseMaps,
           })
-        : [],
-    [chartConfiguration, dashboardLayout, dataMask, isCrossFiltersEnabled],
+        : EMPTY_ARRAY,
+    [chartIds, chartLayoutItems, dataMask, isCrossFiltersEnabled, verboseMaps],
   );
   const { filterControlFactory, filtersWithValues } = useFilterControlFactory(
     dataMaskSelected,
@@ -154,18 +150,27 @@ const FilterControls: FC<FilterControlsProps> = ({
     [filtersWithValues, portalNodes],
   );
 
-  const renderVerticalContent = () => (
-    <>
-      {filtersInScope.map(renderer)}
-      {showCollapsePanel && (
-        <FiltersOutOfScopeCollapsible
-          filtersOutOfScope={filtersOutOfScope}
-          forceRender={hasRequiredFirst}
-          hasTopMargin={filtersInScope.length > 0}
-          renderer={renderer}
-        />
-      )}
-    </>
+  const renderVerticalContent = useCallback(
+    () => (
+      <>
+        {filtersInScope.map(renderer)}
+        {showCollapsePanel && (
+          <FiltersOutOfScopeCollapsible
+            filtersOutOfScope={filtersOutOfScope}
+            forceRender={hasRequiredFirst}
+            hasTopMargin={filtersInScope.length > 0}
+            renderer={renderer}
+          />
+        )}
+      </>
+    ),
+    [
+      filtersInScope,
+      renderer,
+      showCollapsePanel,
+      filtersOutOfScope,
+      hasRequiredFirst,
+    ],
   );
 
   const overflowedFiltersInScope = useMemo(
@@ -230,70 +235,84 @@ const FilterControls: FC<FilterControlsProps> = ({
     return [...crossFilters, ...nativeFiltersInScope];
   }, [filtersInScope, renderer, rendererCrossFilter, selectedCrossFilters]);
 
-  const renderHorizontalContent = () => (
-    <div
-      css={(theme: SupersetTheme) => css`
-        padding: 0 ${theme.gridUnit * 4}px;
-        min-width: 0;
-        flex: 1;
-      `}
-    >
-      <DropdownContainer
-        items={items}
-        dropdownTriggerIcon={
-          <Icons.FilterSmall
-            css={css`
-              && {
-                margin-right: -4px;
-                display: flex;
-              }
-            `}
-          />
-        }
-        dropdownTriggerText={t('More filters')}
-        dropdownTriggerCount={activeOverflowedFiltersInScope.length}
-        dropdownTriggerTooltip={
-          activeOverflowedFiltersInScope.length === 0
-            ? t('No applied filters')
-            : t(
-                'Applied filters: %s',
-                activeOverflowedFiltersInScope
-                  .map(filter => filter.name)
-                  .join(', '),
-              )
-        }
-        dropdownContent={
-          overflowedFiltersInScope.length ||
-          overflowedCrossFilters.length ||
-          (filtersOutOfScope.length && showCollapsePanel)
-            ? () => (
-                <FiltersDropdownContent
-                  overflowedCrossFilters={overflowedCrossFilters}
-                  filtersInScope={overflowedFiltersInScope}
-                  filtersOutOfScope={filtersOutOfScope}
-                  renderer={renderer}
-                  rendererCrossFilter={rendererCrossFilter}
-                  showCollapsePanel={showCollapsePanel}
-                  forceRenderOutOfScope={hasRequiredFirst}
-                />
-              )
-            : undefined
-        }
-        forceRender={hasRequiredFirst}
-        ref={popoverRef}
-        onOverflowingStateChange={({ overflowed: nextOverflowedIds }) => {
-          if (
-            nextOverflowedIds.length !== overflowedIds.length ||
-            overflowedIds.reduce(
-              (a, b, i) => a || b !== nextOverflowedIds[i],
-              false,
-            )
-          ) {
-            setOverflowedIds(nextOverflowedIds);
+  const renderHorizontalContent = useCallback(
+    () => (
+      <div
+        css={(theme: SupersetTheme) => css`
+          padding: 0 ${theme.gridUnit * 4}px;
+          min-width: 0;
+          flex: 1;
+        `}
+      >
+        <DropdownContainer
+          items={items}
+          dropdownTriggerIcon={
+            <Icons.FilterSmall
+              css={css`
+                && {
+                  margin-right: -4px;
+                  display: flex;
+                }
+              `}
+            />
           }
-        }}
-      />
-    </div>
+          dropdownTriggerText={t('More filters')}
+          dropdownTriggerCount={activeOverflowedFiltersInScope.length}
+          dropdownTriggerTooltip={
+            activeOverflowedFiltersInScope.length === 0
+              ? t('No applied filters')
+              : t(
+                  'Applied filters: %s',
+                  activeOverflowedFiltersInScope
+                    .map(filter => filter.name)
+                    .join(', '),
+                )
+          }
+          dropdownContent={
+            overflowedFiltersInScope.length ||
+            overflowedCrossFilters.length ||
+            (filtersOutOfScope.length && showCollapsePanel)
+              ? () => (
+                  <FiltersDropdownContent
+                    overflowedCrossFilters={overflowedCrossFilters}
+                    filtersInScope={overflowedFiltersInScope}
+                    filtersOutOfScope={filtersOutOfScope}
+                    renderer={renderer}
+                    rendererCrossFilter={rendererCrossFilter}
+                    showCollapsePanel={showCollapsePanel}
+                    forceRenderOutOfScope={hasRequiredFirst}
+                  />
+                )
+              : undefined
+          }
+          forceRender={hasRequiredFirst}
+          ref={popoverRef}
+          onOverflowingStateChange={({ overflowed: nextOverflowedIds }) => {
+            if (
+              nextOverflowedIds.length !== overflowedIds.length ||
+              overflowedIds.reduce(
+                (a, b, i) => a || b !== nextOverflowedIds[i],
+                false,
+              )
+            ) {
+              setOverflowedIds(nextOverflowedIds);
+            }
+          }}
+        />
+      </div>
+    ),
+    [
+      items,
+      activeOverflowedFiltersInScope,
+      overflowedFiltersInScope,
+      overflowedCrossFilters,
+      filtersOutOfScope,
+      showCollapsePanel,
+      renderer,
+      rendererCrossFilter,
+      hasRequiredFirst,
+      overflowedIds,
+    ],
   );
 
   const overflowedByIndex = useMemo(() => {
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
index 45ccd4dd41..454d906032 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
@@ -221,7 +221,7 @@ const FilterValue: FC<FilterControlProps> = ({
     datasetId,
     groupby,
     handleFilterLoadFinish,
-    JSON.stringify(filter),
+    filter,
     hasDataSource,
     isRefreshing,
     shouldRefresh,
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
index 2b96d9963f..e97a8768e0 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
@@ -17,18 +17,19 @@
  * under the License.
  */
 
-import { FC, memo } from 'react';
+import { FC, memo, useMemo } from 'react';
 import {
   DataMaskStateWithId,
   FeatureFlag,
   isFeatureEnabled,
-  JsonObject,
   styled,
   t,
 } from '@superset-ui/core';
 import Icons from 'src/components/Icons';
 import Loading from 'src/components/Loading';
-import { DashboardLayout, RootState } from 'src/dashboard/types';
+import { RootState } from 'src/dashboard/types';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
+import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
 import { useSelector } from 'react-redux';
 import FilterControls from './FilterControls/FilterControls';
 import { useChartsVerboseMaps, getFilterBarTestId } from './utils';
@@ -36,6 +37,7 @@ import { HorizontalBarProps } from './types';
 import FilterBarSettings from './FilterBarSettings';
 import FilterConfigurationLink from './FilterConfigurationLink';
 import crossFiltersSelector from './CrossFilters/selectors';
+import { CrossFilterIndicator } from '../selectors';
 
 const HorizontalBar = styled.div`
   ${({ theme }) => `
@@ -96,6 +98,7 @@ const FiltersLinkContainer = styled.div<{ hasFilters: boolean 
}>`
   `}
 `;
 
+const EMPTY_ARRAY: CrossFilterIndicator[] = [];
 const HorizontalFilterBar: FC<HorizontalBarProps> = ({
   actions,
   canEdit,
@@ -108,25 +111,26 @@ const HorizontalFilterBar: FC<HorizontalBarProps> = ({
   const dataMask = useSelector<RootState, DataMaskStateWithId>(
     state => state.dataMask,
   );
-  const chartConfiguration = useSelector<RootState, JsonObject>(
-    state => state.dashboardInfo.metadata?.chart_configuration,
-  );
-  const dashboardLayout = useSelector<RootState, DashboardLayout>(
-    state => state.dashboardLayout.present,
-  );
+  const chartIds = useChartIds();
+  const chartLayoutItems = useChartLayoutItems();
   const isCrossFiltersEnabled = isFeatureEnabled(
     FeatureFlag.DashboardCrossFilters,
   );
   const verboseMaps = useChartsVerboseMaps();
 
-  const selectedCrossFilters = isCrossFiltersEnabled
-    ? crossFiltersSelector({
-        dataMask,
-        chartConfiguration,
-        dashboardLayout,
-        verboseMaps,
-      })
-    : [];
+  const selectedCrossFilters = useMemo(
+    () =>
+      isCrossFiltersEnabled
+        ? crossFiltersSelector({
+            dataMask,
+            chartIds,
+            chartLayoutItems,
+            verboseMaps,
+          })
+        : EMPTY_ARRAY,
+    [chartIds, chartLayoutItems, dataMask, isCrossFiltersEnabled, verboseMaps],
+  );
+
   const hasFilters = filterValues.length > 0 || selectedCrossFilters.length > 
0;
 
   return (
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
index c25134715d..0b64d37a25 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
@@ -24,8 +24,8 @@ import {
   useEffect,
   useState,
   useCallback,
-  createContext,
   useRef,
+  useMemo,
 } from 'react';
 
 import { useDispatch, useSelector } from 'react-redux';
@@ -129,7 +129,6 @@ const publishDataMask = debounce(
   SLOW_DEBOUNCE,
 );
 
-export const FilterBarScrollContext = createContext(false);
 const FilterBar: FC<FiltersBarProps> = ({
   orientation = FilterBarOrientation.Vertical,
   verticalConfig,
@@ -144,8 +143,11 @@ const FilterBar: FC<FiltersBarProps> = ({
   const tabId = useTabId();
   const filters = useFilters();
   const previousFilters = usePrevious(filters);
-  const filterValues = Object.values(filters);
-  const nativeFilterValues = filterValues.filter(isNativeFilter);
+  const filterValues = useMemo(() => Object.values(filters), [filters]);
+  const nativeFilterValues = useMemo(
+    () => filterValues.filter(isNativeFilter),
+    [filterValues],
+  );
   const dashboardId = useSelector<any, number>(
     ({ dashboardInfo }) => dashboardInfo?.id,
   );
@@ -212,14 +214,9 @@ const FilterBar: FC<FiltersBarProps> = ({
 
       if (!isEmpty(updates)) {
         setDataMaskSelected(draft => ({ ...draft, ...updates }));
-        Object.keys(updates).forEach(key => dispatch(clearDataMask(key)));
       }
     }
-  }, [
-    JSON.stringify(filters),
-    JSON.stringify(previousFilters),
-    previousDashboardId,
-  ]);
+  }, [dashboardId, filters, previousDashboardId, setDataMaskSelected]);
 
   const dataMaskAppliedText = JSON.stringify(dataMaskApplied);
 
@@ -276,16 +273,27 @@ const FilterBar: FC<FiltersBarProps> = ({
   );
   const isInitialized = useInitialization();
 
-  const actions = (
-    <ActionButtons
-      filterBarOrientation={orientation}
-      width={verticalConfig?.width}
-      onApply={handleApply}
-      onClearAll={handleClearAll}
-      dataMaskSelected={dataMaskSelected}
-      dataMaskApplied={dataMaskApplied}
-      isApplyDisabled={isApplyDisabled}
-    />
+  const actions = useMemo(
+    () => (
+      <ActionButtons
+        filterBarOrientation={orientation}
+        width={verticalConfig?.width}
+        onApply={handleApply}
+        onClearAll={handleClearAll}
+        dataMaskSelected={dataMaskSelected}
+        dataMaskApplied={dataMaskApplied}
+        isApplyDisabled={isApplyDisabled}
+      />
+    ),
+    [
+      orientation,
+      verticalConfig?.width,
+      handleApply,
+      handleClearAll,
+      dataMaskSelected,
+      dataMaskAppliedText,
+      isApplyDisabled,
+    ],
   );
 
   const filterBarComponent =
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterOutlined.ts
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterOutlined.ts
index 05c9b671f8..b2448fcce7 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterOutlined.ts
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterOutlined.ts
@@ -18,17 +18,26 @@
  */
 
 import { useSelector } from 'react-redux';
+import { createSelector } from '@reduxjs/toolkit';
 import { RootState } from 'src/dashboard/types';
 import getChartAndLabelComponentIdFromPath from 
'src/dashboard/util/getChartAndLabelComponentIdFromPath';
 
+const filterOutlinedSelector = createSelector(
+  [
+    (state: RootState) => state.dashboardState.directPathToChild,
+    (state: RootState) => state.dashboardState.directPathLastUpdated,
+  ],
+  (directPathToChild, directPathLastUpdated) => ({
+    outlinedFilterId: (
+      getChartAndLabelComponentIdFromPath(directPathToChild || []) as Record<
+        string,
+        string
+      >
+    )?.native_filter,
+    lastUpdated: directPathLastUpdated,
+  }),
+);
 export const useFilterOutlined = () =>
   useSelector<RootState, { outlinedFilterId: string; lastUpdated: number }>(
-    state => ({
-      outlinedFilterId: (
-        getChartAndLabelComponentIdFromPath(
-          state.dashboardState.directPathToChild || [],
-        ) as Record<string, string>
-      )?.native_filter,
-      lastUpdated: state.dashboardState.directPathLastUpdated,
-    }),
+    filterOutlinedSelector,
   );
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
index cdf488bc3d..6a6ca3a3e3 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
@@ -22,6 +22,7 @@ import { DataMaskStateWithId, Filter, FilterState } from 
'@superset-ui/core';
 import { testWithId } from 'src/utils/testUtils';
 import { RootState } from 'src/dashboard/types';
 import { useSelector } from 'react-redux';
+import { createSelector } from '@reduxjs/toolkit';
 
 export const getOnlyExtraFormData = (data: DataMaskStateWithId) =>
   Object.values(data).reduce(
@@ -64,20 +65,26 @@ export const checkIsApplyDisabled = (
   );
 };
 
+const chartsVerboseMapSelector = createSelector(
+  [
+    (state: RootState) => state.sliceEntities.slices,
+    (state: RootState) => state.datasources,
+  ],
+  (slices, datasources) =>
+    Object.keys(slices).reduce((chartsVerboseMaps, chartId) => {
+      const chartDatasource = slices[chartId]?.datasource
+        ? datasources[slices[chartId].datasource]
+        : undefined;
+      return {
+        ...chartsVerboseMaps,
+        [chartId]: chartDatasource ? chartDatasource.verbose_map : {},
+      };
+    }, {}),
+);
+
 export const useChartsVerboseMaps = () =>
   useSelector<RootState, { [chartId: string]: Record<string, string> }>(
-    state => {
-      const { charts, datasources } = state;
-
-      return Object.keys(state.charts).reduce((chartsVerboseMaps, chartId) => {
-        const chartDatasource =
-          datasources[charts[chartId]?.form_data?.datasource];
-        return {
-          ...chartsVerboseMaps,
-          [chartId]: chartDatasource ? chartDatasource.verbose_map : {},
-        };
-      }, {});
-    },
+    chartsVerboseMapSelector,
   );
 
 export const FILTER_BAR_TEST_ID = 'filter-bar';
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx
index 319fecec0a..2f2f4e4525 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx
@@ -86,6 +86,9 @@ const baseInitialState = {
       id: 3,
     },
   },
+  dashboardState: {
+    sliceIds: [1, 2, 3],
+  },
   dashboardLayout: {
     past: [],
     future: [],
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts 
b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
index 36f0fd1925..232ff57de2 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
@@ -32,11 +32,7 @@ import {
 } from '@superset-ui/core';
 import { TIME_FILTER_MAP } from 'src/explore/constants';
 import { getChartIdsInFilterScope } from 
'src/dashboard/util/activeDashboardFilters';
-import {
-  ChartConfiguration,
-  DashboardLayout,
-  Layout,
-} from 'src/dashboard/types';
+import { ChartConfiguration, LayoutItem } from 'src/dashboard/types';
 import { areObjectsEqual } from 'src/reduxUtils';
 
 export enum IndicatorStatus {
@@ -170,7 +166,7 @@ export type CrossFilterIndicator = Indicator & { emitterId: 
number };
 export const getCrossFilterIndicator = (
   chartId: number,
   dataMask: DataMask,
-  dashboardLayout: DashboardLayout,
+  chartLayoutItems: LayoutItem[],
 ) => {
   const filterState = dataMask?.filterState;
   const filters = dataMask?.extraFormData?.filters;
@@ -179,19 +175,17 @@ export const getCrossFilterIndicator = (
   const column =
     filters?.[0]?.col || (filtersState && Object.keys(filtersState)[0]);
 
-  const dashboardLayoutItem = Object.values(dashboardLayout).find(
+  const chartLayoutItem = chartLayoutItems.find(
     layoutItem => layoutItem?.meta?.chartId === chartId,
   );
+
   const filterObject: Indicator = {
     column,
     name:
-      dashboardLayoutItem?.meta?.sliceNameOverride ||
-      dashboardLayoutItem?.meta?.sliceName ||
+      chartLayoutItem?.meta?.sliceNameOverride ||
+      chartLayoutItem?.meta?.sliceName ||
       '',
-    path: [
-      ...(dashboardLayoutItem?.parents ?? []),
-      dashboardLayoutItem?.id || '',
-    ],
+    path: [...(chartLayoutItem?.parents ?? []), chartLayoutItem?.id || ''],
     value: label,
   };
   return filterObject;
@@ -288,7 +282,7 @@ const defaultChartConfig = {};
 export const selectChartCrossFilters = (
   dataMask: DataMaskStateWithId,
   chartId: number,
-  dashboardLayout: Layout,
+  chartLayoutItems: LayoutItem[],
   chartConfiguration: ChartConfiguration = defaultChartConfig,
   appliedColumns: Set<string>,
   rejectedColumns: Set<string>,
@@ -312,7 +306,7 @@ export const selectChartCrossFilters = (
         const filterIndicator = getCrossFilterIndicator(
           Number(chartConfig.id),
           dataMask[chartConfig.id],
-          dashboardLayout,
+          chartLayoutItems,
         );
         const filterStatus = getStatus({
           label: filterIndicator.value,
@@ -339,7 +333,7 @@ export const selectNativeIndicatorsForChart = (
   dataMask: DataMaskStateWithId,
   chartId: number,
   chart: any,
-  dashboardLayout: Layout,
+  chartLayoutItems: LayoutItem[],
   chartConfiguration: ChartConfiguration = defaultChartConfig,
 ): Indicator[] => {
   const appliedColumns = getAppliedColumns(chart);
@@ -351,7 +345,7 @@ export const selectNativeIndicatorsForChart = (
     areObjectsEqual(cachedFilterData?.appliedColumns, appliedColumns) &&
     areObjectsEqual(cachedFilterData?.rejectedColumns, rejectedColumns) &&
     cachedFilterData?.nativeFilters === nativeFilters &&
-    cachedFilterData?.dashboardLayout === dashboardLayout &&
+    cachedFilterData?.chartLayoutItems === chartLayoutItems &&
     cachedFilterData?.chartConfiguration === chartConfiguration &&
     cachedFilterData?.dataMask === dataMask
   ) {
@@ -389,7 +383,7 @@ export const selectNativeIndicatorsForChart = (
     crossFilterIndicators = selectChartCrossFilters(
       dataMask,
       chartId,
-      dashboardLayout,
+      chartLayoutItems,
       chartConfiguration,
       appliedColumns,
       rejectedColumns,
@@ -399,7 +393,7 @@ export const selectNativeIndicatorsForChart = (
   cachedNativeIndicatorsForChart[chartId] = indicators;
   cachedNativeFilterDataForChart[chartId] = {
     nativeFilters,
-    dashboardLayout,
+    chartLayoutItems,
     chartConfiguration,
     dataMask,
     appliedColumns,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.ts 
b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
index 5bf71116c3..a17ebc15d8 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/state.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 import { useSelector } from 'react-redux';
-import { useMemo } from 'react';
+import { useCallback, useMemo } from 'react';
 import {
   Filter,
   FilterConfiguration,
@@ -25,7 +25,7 @@ import {
   isFilterDivider,
 } from '@superset-ui/core';
 import { ActiveTabs, DashboardLayout, RootState } from '../../types';
-import { TAB_TYPE } from '../../util/componentTypes';
+import { CHART_TYPE, TAB_TYPE } from '../../util/componentTypes';
 
 const defaultFilterConfiguration: Filter[] = [];
 
@@ -79,34 +79,45 @@ function useActiveDashboardTabs() {
 
 function useSelectChartTabParents() {
   const dashboardLayout = useDashboardLayout();
-  return (chartId: number) => {
-    const chartLayoutItem = Object.values(dashboardLayout).find(
-      layoutItem => layoutItem.meta?.chartId === chartId,
-    );
-    return chartLayoutItem?.parents?.filter(
-      (parent: string) => dashboardLayout[parent]?.type === TAB_TYPE,
-    );
-  };
+  const layoutChartItems = useMemo(
+    () =>
+      Object.values(dashboardLayout).filter(item => item.type === CHART_TYPE),
+    [dashboardLayout],
+  );
+  return useCallback(
+    (chartId: number) => {
+      const chartLayoutItem = layoutChartItems.find(
+        layoutItem => layoutItem.meta?.chartId === chartId,
+      );
+      return chartLayoutItem?.parents?.filter(
+        (parent: string) => dashboardLayout[parent]?.type === TAB_TYPE,
+      );
+    },
+    [dashboardLayout, layoutChartItems],
+  );
 }
 
 export function useIsFilterInScope() {
   const activeTabs = useActiveDashboardTabs();
   const selectChartTabParents = useSelectChartTabParents();
 
-  // Filter is in scope if any of it's charts is visible.
+  // Filter is in scope if any of its charts is visible.
   // Chart is visible if it's placed in an active tab tree or if it's not 
attached to any tab.
-  // Chart is in an active tab tree if all of it's ancestors of type TAB are 
active
+  // Chart is in an active tab tree if all of its ancestors of type TAB are 
active
   // Dividers are always in scope
-  return (filter: Filter | Divider) =>
-    isFilterDivider(filter) ||
-    ('chartsInScope' in filter &&
-      filter.chartsInScope?.some((chartId: number) => {
-        const tabParents = selectChartTabParents(chartId);
-        return (
-          tabParents?.length === 0 ||
-          tabParents?.every(tab => activeTabs.includes(tab))
-        );
-      }));
+  return useCallback(
+    (filter: Filter | Divider) =>
+      isFilterDivider(filter) ||
+      ('chartsInScope' in filter &&
+        filter.chartsInScope?.some((chartId: number) => {
+          const tabParents = selectChartTabParents(chartId);
+          return (
+            tabParents?.length === 0 ||
+            tabParents?.every(tab => activeTabs.includes(tab))
+          );
+        })),
+    [selectChartTabParents, activeTabs],
+  );
 }
 
 export function useSelectFiltersInScope(filters: (Filter | Divider)[]) {
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts 
b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
index 095a9edaf3..db21db547d 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
@@ -19,6 +19,7 @@
 import { Behavior, FeatureFlag } from '@superset-ui/core';
 import * as uiCore from '@superset-ui/core';
 import { DashboardLayout } from 'src/dashboard/types';
+import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
 import { nativeFilterGate, findTabsWithChartsInScope } from './utils';
 
 let isFeatureEnabledMock: jest.MockInstance<boolean, [feature: FeatureFlag]>;
@@ -119,7 +120,10 @@ test('findTabsWithChartsInScope should handle a recursive 
layout structure', ()
     },
   } as any as DashboardLayout;
 
-  expect(Array.from(findTabsWithChartsInScope(dashboardLayout, []))).toEqual(
+  const chartLayoutItems = Object.values(dashboardLayout).filter(
+    item => item.type === CHART_TYPE,
+  );
+  expect(Array.from(findTabsWithChartsInScope(chartLayoutItems, []))).toEqual(
     [],
   );
 });
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts 
b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
index 0ba28d0a3a..734e0ce91f 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
@@ -30,10 +30,9 @@ import {
   QueryFormData,
   t,
 } from '@superset-ui/core';
-import { DashboardLayout } from 'src/dashboard/types';
+import { LayoutItem } from 'src/dashboard/types';
 import extractUrlParams from 'src/dashboard/util/extractUrlParams';
-import { CHART_TYPE, TAB_TYPE } from '../../util/componentTypes';
-import { DASHBOARD_GRID_ID, DASHBOARD_ROOT_ID } from '../../util/constants';
+import { TAB_TYPE } from '../../util/componentTypes';
 import getBootstrapData from '../../../utils/getBootstrapData';
 
 const getDefaultRowLimit = (): number => {
@@ -156,84 +155,20 @@ export function nativeFilterGate(behaviors: Behavior[]): 
boolean {
   );
 }
 
-const isComponentATab = (
-  dashboardLayout: DashboardLayout,
-  componentId: string,
-) => dashboardLayout?.[componentId]?.type === TAB_TYPE;
-
-const findTabsWithChartsInScopeHelper = (
-  dashboardLayout: DashboardLayout,
-  chartsInScope: number[],
-  componentId: string,
-  tabIds: string[],
-  tabsToHighlight: Set<string>,
-  visited: Set<string>,
-) => {
-  if (visited.has(componentId)) {
-    return;
-  }
-  visited.add(componentId);
-  if (
-    dashboardLayout?.[componentId]?.type === CHART_TYPE &&
-    chartsInScope.includes(dashboardLayout[componentId]?.meta?.chartId)
-  ) {
-    tabIds.forEach(tabsToHighlight.add, tabsToHighlight);
-  }
-  if (
-    dashboardLayout?.[componentId]?.children?.length === 0 ||
-    (isComponentATab(dashboardLayout, componentId) &&
-      tabsToHighlight.has(componentId))
-  ) {
-    return;
-  }
-  dashboardLayout[componentId]?.children.forEach(childId =>
-    findTabsWithChartsInScopeHelper(
-      dashboardLayout,
-      chartsInScope,
-      childId,
-      isComponentATab(dashboardLayout, childId) ? [...tabIds, childId] : 
tabIds,
-      tabsToHighlight,
-      visited,
-    ),
-  );
-};
-
 export const findTabsWithChartsInScope = (
-  dashboardLayout: DashboardLayout,
+  chartLayoutItems: LayoutItem[],
   chartsInScope: number[],
-) => {
-  const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
-  const rootChildId = dashboardRoot.children[0];
-  const hasTopLevelTabs = rootChildId !== DASHBOARD_GRID_ID;
-  const tabsInScope = new Set<string>();
-  const visited = new Set<string>();
-  if (hasTopLevelTabs) {
-    dashboardLayout[rootChildId]?.children?.forEach(tabId =>
-      findTabsWithChartsInScopeHelper(
-        dashboardLayout,
-        chartsInScope,
-        tabId,
-        [tabId],
-        tabsInScope,
-        visited,
-      ),
-    );
-  } else {
-    Object.values(dashboardLayout)
-      .filter(element => element?.type === TAB_TYPE)
-      .forEach(element =>
-        findTabsWithChartsInScopeHelper(
-          dashboardLayout,
-          chartsInScope,
-          element.id,
-          [element.id],
-          tabsInScope,
-          visited,
-        ),
-      );
-  }
-  return tabsInScope;
-};
+) =>
+  new Set<string>(
+    chartsInScope
+      .map(chartId =>
+        chartLayoutItems
+          .find(item => item?.meta?.chartId === chartId)
+          ?.parents?.filter(parent => parent.startsWith(`${TAB_TYPE}-`)),
+      )
+      .filter(id => id !== undefined)
+      .flat() as string[],
+  );
 
 export const getFilterValueForDisplay = (
   value?: string[] | null | string | number | object,
diff --git a/superset-frontend/src/dashboard/util/charts/useChartIds.ts 
b/superset-frontend/src/dashboard/util/charts/useChartIds.ts
index c95327a94a..9afc16d9f4 100644
--- a/superset-frontend/src/dashboard/util/charts/useChartIds.ts
+++ b/superset-frontend/src/dashboard/util/charts/useChartIds.ts
@@ -17,20 +17,7 @@
  * under the License.
  */
 import { useSelector } from 'react-redux';
-import { isEqual } from 'lodash';
-import { createSelector } from '@reduxjs/toolkit';
 import { RootState } from 'src/dashboard/types';
-import { useMemoCompare } from 'src/hooks/useMemoCompare';
 
-const chartIdsSelector = createSelector(
-  (state: RootState) => state.charts,
-  charts => Object.values(charts).map(chart => chart.id),
-);
-
-export const useChartIds = () => {
-  const chartIds = useSelector<RootState, number[]>(chartIdsSelector);
-  return useMemoCompare(
-    chartIds,
-    (prev, next) => prev === next || isEqual(prev, next),
-  );
-};
+export const useChartIds = () =>
+  useSelector<RootState, number[]>(state => state.dashboardState.sliceIds);
diff --git a/superset-frontend/src/dashboard/util/crossFilters.ts 
b/superset-frontend/src/dashboard/util/crossFilters.ts
index 903a1f74fb..123435a430 100644
--- a/superset-frontend/src/dashboard/util/crossFilters.ts
+++ b/superset-frontend/src/dashboard/util/crossFilters.ts
@@ -33,6 +33,7 @@ import {
   isCrossFilterScopeGlobal,
 } from '../types';
 import { DEFAULT_CROSS_FILTER_SCOPING } from '../constants';
+import { CHART_TYPE } from './componentTypes';
 
 export const isCrossFiltersEnabled = (
   metadataCrossFiltersEnabled: boolean | undefined,
@@ -52,13 +53,17 @@ export const getCrossFiltersConfiguration = (
     return undefined;
   }
 
+  const chartLayoutItems = Object.values(dashboardLayout).filter(
+    item => item?.type === CHART_TYPE,
+  );
+
   const globalChartConfiguration = metadata.global_chart_configuration?.scope
     ? {
         scope: metadata.global_chart_configuration.scope,
         chartsInScope: getChartIdsInFilterScope(
           metadata.global_chart_configuration.scope,
           Object.values(charts).map(chart => chart.id),
-          dashboardLayout,
+          chartLayoutItems,
         ),
       }
     : {
@@ -69,7 +74,7 @@ export const getCrossFiltersConfiguration = (
   // If user just added cross filter to dashboard it's not saving its scope on 
server,
   // so we tweak it until user will update scope and will save it in server
   const chartConfiguration = {};
-  Object.values(dashboardLayout).forEach(layoutItem => {
+  chartLayoutItems.forEach(layoutItem => {
     const chartId = layoutItem.meta?.chartId;
 
     if (!isDefined(chartId)) {
@@ -105,7 +110,7 @@ export const getCrossFiltersConfiguration = (
           : getChartIdsInFilterScope(
               chartConfiguration[chartId].crossFilters.scope,
               Object.values(charts).map(chart => chart.id),
-              dashboardLayout,
+              chartLayoutItems,
             );
     }
   });
diff --git a/superset-frontend/src/dashboard/util/getChartIdsInFilterScope.ts 
b/superset-frontend/src/dashboard/util/getChartIdsInFilterScope.ts
index 7e4b7b1273..5bd37f26bc 100644
--- a/superset-frontend/src/dashboard/util/getChartIdsInFilterScope.ts
+++ b/superset-frontend/src/dashboard/util/getChartIdsInFilterScope.ts
@@ -18,14 +18,13 @@
  */
 import { NativeFilterScope } from '@superset-ui/core';
 import { CHART_TYPE } from './componentTypes';
-import { Layout } from '../types';
+import { LayoutItem } from '../types';
 
 export function getChartIdsInFilterScope(
   filterScope: NativeFilterScope,
   chartIds: number[],
-  layout: Layout,
+  layoutItems: LayoutItem[],
 ) {
-  const layoutItems = Object.values(layout);
   return chartIds.filter(
     chartId =>
       !filterScope.excluded.includes(chartId) &&
diff --git a/superset-frontend/src/dashboard/util/charts/useChartIds.ts 
b/superset-frontend/src/dashboard/util/useChartLayoutItems.ts
similarity index 64%
copy from superset-frontend/src/dashboard/util/charts/useChartIds.ts
copy to superset-frontend/src/dashboard/util/useChartLayoutItems.ts
index c95327a94a..10635fc05b 100644
--- a/superset-frontend/src/dashboard/util/charts/useChartIds.ts
+++ b/superset-frontend/src/dashboard/util/useChartLayoutItems.ts
@@ -16,21 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { useSelector } from 'react-redux';
-import { isEqual } from 'lodash';
 import { createSelector } from '@reduxjs/toolkit';
-import { RootState } from 'src/dashboard/types';
-import { useMemoCompare } from 'src/hooks/useMemoCompare';
+import { useSelector } from 'react-redux';
+import { RootState } from '../types';
+import { CHART_TYPE } from './componentTypes';
 
-const chartIdsSelector = createSelector(
-  (state: RootState) => state.charts,
-  charts => Object.values(charts).map(chart => chart.id),
+const chartLayoutItemsSelector = createSelector(
+  (state: RootState) => state.dashboardLayout.present,
+  layout => Object.values(layout).filter(item => item?.type === CHART_TYPE),
 );
 
-export const useChartIds = () => {
-  const chartIds = useSelector<RootState, number[]>(chartIdsSelector);
-  return useMemoCompare(
-    chartIds,
-    (prev, next) => prev === next || isEqual(prev, next),
-  );
-};
+export const useChartLayoutItems = () => useSelector(chartLayoutItemsSelector);
diff --git 
a/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.test.tsx 
b/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.test.tsx
index 80e6038c07..a2047bf2aa 100644
--- 
a/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.test.tsx
+++ 
b/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.test.tsx
@@ -141,54 +141,4 @@ describe('useFilterFocusHighlightStyles', () => {
     const styles = getComputedStyle(container);
     expect(parseFloat(styles.opacity)).toBe(1);
   });
-
-  it('should return unfocused styles if focusedFilterField is targeting a 
different chart', async () => {
-    const chartId = 18;
-    mockGetRelatedCharts.mockReturnValue([]);
-    const store = createMockStore({
-      dashboardState: {
-        focusedFilterField: {
-          chartId: 10,
-          column: 'test',
-        },
-      },
-      dashboardFilters: {
-        10: {
-          scopes: {},
-        },
-      },
-    });
-    renderWrapper(chartId, store);
-
-    const container = screen.getByTestId('test-component');
-
-    const styles = getComputedStyle(container);
-    expect(parseFloat(styles.opacity)).toBe(0.3);
-  });
-
-  it('should return focused styles if focusedFilterField chart equals our 
own', async () => {
-    const chartId = 18;
-    mockGetRelatedCharts.mockReturnValue([chartId]);
-    const store = createMockStore({
-      dashboardState: {
-        focusedFilterField: {
-          chartId,
-          column: 'test',
-        },
-      },
-      dashboardFilters: {
-        [chartId]: {
-          scopes: {
-            otherColumn: {},
-          },
-        },
-      },
-    });
-    renderWrapper(chartId, store);
-
-    const container = screen.getByTestId('test-component');
-
-    const styles = getComputedStyle(container);
-    expect(parseFloat(styles.opacity)).toBe(1);
-  });
 });
diff --git 
a/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.ts 
b/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.ts
index aa636cb1ee..3dea0d54ac 100644
--- a/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.ts
+++ b/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.ts
@@ -16,40 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { useMemo } from 'react';
 import { Filter, useTheme } from '@superset-ui/core';
 import { useSelector } from 'react-redux';
 
-import { getChartIdsInFilterScope } from 
'src/dashboard/util/activeDashboardFilters';
-import { DashboardState, RootState } from 'src/dashboard/types';
+import { RootState } from 'src/dashboard/types';
 import { getRelatedCharts } from './getRelatedCharts';
 
-const selectFocusedFilterScope = (
-  dashboardState: DashboardState,
-  dashboardFilters: any,
-) => {
-  if (!dashboardState.focusedFilterField) return null;
-  const { chartId, column } = dashboardState.focusedFilterField;
-  return {
-    chartId,
-    scope: dashboardFilters[chartId].scopes[column],
-  };
-};
+const unfocusedChartStyles = { opacity: 0.3, pointerEvents: 'none' };
+const EMPTY = {};
 
 const useFilterFocusHighlightStyles = (chartId: number) => {
   const theme = useTheme();
 
-  const nativeFilters = useSelector((state: RootState) => state.nativeFilters);
-  const dashboardState = useSelector(
-    (state: RootState) => state.dashboardState,
+  const focusedChartStyles = useMemo(
+    () => ({
+      borderColor: theme.colors.primary.light2,
+      opacity: 1,
+      boxShadow: `0px 0px ${theme.gridUnit * 2}px 
${theme.colors.primary.base}`,
+      pointerEvents: 'auto',
+    }),
+    [theme],
   );
 
-  const dashboardFilters = useSelector(
-    (state: RootState) => state.dashboardFilters,
-  );
-  const focusedFilterScope = selectFocusedFilterScope(
-    dashboardState,
-    dashboardFilters,
-  );
+  const nativeFilters = useSelector((state: RootState) => state.nativeFilters);
 
   const slices =
     useSelector((state: RootState) => state.sliceEntities.slices) || {};
@@ -57,8 +47,8 @@ const useFilterFocusHighlightStyles = (chartId: number) => {
   const highlightedFilterId =
     nativeFilters?.focusedFilterId || nativeFilters?.hoveredFilterId;
 
-  if (!(focusedFilterScope || highlightedFilterId)) {
-    return {};
+  if (!highlightedFilterId) {
+    return EMPTY;
   }
 
   const relatedCharts = getRelatedCharts(
@@ -67,29 +57,7 @@ const useFilterFocusHighlightStyles = (chartId: number) => {
     slices,
   );
 
-  // we use local styles here instead of a conditionally-applied class,
-  // because adding any conditional class to this container
-  // causes performance issues in Chrome.
-
-  // default to the "de-emphasized" state
-  const unfocusedChartStyles = { opacity: 0.3, pointerEvents: 'none' };
-  const focusedChartStyles = {
-    borderColor: theme.colors.primary.light2,
-    opacity: 1,
-    boxShadow: `0px 0px ${theme.gridUnit * 2}px ${theme.colors.primary.base}`,
-    pointerEvents: 'auto',
-  };
-
-  if (highlightedFilterId) {
-    if (relatedCharts.includes(chartId)) {
-      return focusedChartStyles;
-    }
-  } else if (
-    chartId === focusedFilterScope?.chartId ||
-    getChartIdsInFilterScope({
-      filterScope: focusedFilterScope?.scope,
-    }).includes(chartId)
-  ) {
+  if (highlightedFilterId && relatedCharts.includes(chartId)) {
     return focusedChartStyles;
   }
 

Reply via email to