From 9a96dac3a75d33299a1482ef20ffd2462b2f2e66 Mon Sep 17 00:00:00 2001 From: Ville Brofeldt <33317356+villebro@users.noreply.github.com> Date: Tue, 25 May 2021 15:03:22 +0300 Subject: [PATCH] fix(native-filters): loop bug by simplify state handling (#14788) --- .../FiltersConfigForm/DefaultValue.tsx | 1 + .../components/Select/SelectFilterPlugin.tsx | 121 ++++++++---------- 2 files changed, 54 insertions(+), 68 deletions(-) diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx index 0d05add39e..524ab83cd7 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx @@ -73,6 +73,7 @@ const DefaultValue: FC = ({ chartType={formFilter?.filterType} hooks={{ setDataMask }} enableNoResults={enableNoResults} + filterState={formFilter.defaultDataMask?.filterState} /> ); }; diff --git a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx index 4fc7e296ce..0673c5a28f 100644 --- a/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Select/SelectFilterPlugin.tsx @@ -29,7 +29,7 @@ import { } from '@superset-ui/core'; import React, { useCallback, useEffect, useReducer, useState } from 'react'; import { Select } from 'src/common/components'; -import { debounce } from 'lodash'; +import debounce from 'lodash/debounce'; import { SLOW_DEBOUNCE } from 'src/constants'; import { PluginFilterSelectProps, SelectValue } from './types'; import { StyledSelect, Styles } from '../common'; @@ -91,7 +91,6 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { appSection, } = props; const { - defaultValue, enableEmptyFilter, multiSelect, showSearch, @@ -100,29 +99,36 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { defaultToFirstItem, searchAllOptions, } = formData; + const groupby = ensureIsArray(formData.groupby); + const [col] = groupby; + const [currentSuggestionSearch, setCurrentSuggestionSearch] = useState(''); + const [dataMask, dispatchDataMask] = useReducer(reducer, { + filterState, + ownState: { + coltypeMap, + }, + }); + const updateDataMask = (values: SelectValue) => { + const emptyFilter = + enableEmptyFilter && !inverseSelection && !values?.length; + + dispatchDataMask({ + type: 'filterState', + extraFormData: getSelectExtraFormData( + col, + values, + emptyFilter, + inverseSelection, + ), + filterState: { + value: values, + }, + }); + }; const isDisabled = appSection === AppSection.FILTER_CONFIG_MODAL && defaultToFirstItem; - const groupby = ensureIsArray(formData.groupby); - // Correct initial value for Ant Select - - // If we are in config modal we always need show empty select for `defaultToFirstItem` - const [values, setValues] = useState( - !isDisabled && defaultValue?.length ? defaultValue : [], - ); - const [currentSuggestionSearch, setCurrentSuggestionSearch] = useState(''); - const [dataMask, dispatchDataMask] = useReducer( - reducer, - searchAllOptions - ? { - ownState: { - coltypeMap, - }, - } - : {}, - ); - const debouncedOwnStateFunc = useCallback( debounce((val: string) => { dispatchDataMask({ @@ -154,67 +160,46 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { } }; - useEffect(() => { - const firstItem: SelectValue = data[0] - ? (groupby.map(col => data[0][col]) as string[]) - : null; - if (!isDisabled && defaultToFirstItem && firstItem) { - // initialize to first value if set to default to first item - setValues(firstItem); - } else if (!isDisabled && defaultValue?.length) { - // initialize to saved value - setValues(defaultValue); - } - }, [defaultToFirstItem, defaultValue]); - const handleBlur = () => { clearSuggestionSearch(); unsetFocusedFilter(); }; - const [col] = groupby; const datatype: GenericDataType = coltypeMap[col]; const labelFormatter = getDataRecordFormatter({ timeFormatter: smartDateDetailedFormatter, }); const handleChange = (value?: SelectValue | number | string) => { - setValues(ensureIsArray(value)); + const values = ensureIsArray(value); + if (values.length === 0) { + updateDataMask(null); + } else { + updateDataMask(values); + } }; useEffect(() => { + const firstItem: SelectValue = data[0] + ? (groupby.map(col => data[0][col]) as string[]) + : null; if (isDisabled) { - setValues([]); + // empty selection if filter is disabled + updateDataMask(null); + } else if (!isDisabled && defaultToFirstItem && firstItem) { + // initialize to first value if set to default to first item + updateDataMask(firstItem); + } else { + // reset data mask based on filter state + updateDataMask(filterState.value); } - }, [isDisabled]); - - useEffect(() => { - const emptyFilter = - enableEmptyFilter && !inverseSelection && values?.length === 0; - - dispatchDataMask({ - type: 'filterState', - extraFormData: getSelectExtraFormData( - col, - values, - emptyFilter, - inverseSelection, - ), - filterState: { - // We need to save in state `FIRST_VALUE` as some const and not as REAL value, - // because when FiltersBar check if all filters initialized it compares `defaultValue` with this value - // and because REAL value can be unpredictable for users that have different data for same dashboard we use `FIRST_VALUE` - value: values, - }, - }); - }, [col, enableEmptyFilter, inverseSelection, JSON.stringify(values)]); - - useEffect(() => { - // handle changes coming from application, e.g. "Clear all" button - if (JSON.stringify(values) !== JSON.stringify(filterState.value)) { - handleChange(filterState.value); - } - }, [JSON.stringify(filterState.value)]); + }, [ + col, + isDisabled, + defaultToFirstItem, + enableEmptyFilter, + inverseSelection, + ]); useEffect(() => { setDataMask(dataMask); @@ -229,7 +214,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { suggestion === currentSuggestionSearch, ) && (