-
Notifications
You must be signed in to change notification settings - Fork 14.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(native-filters): fix loop caused by external state handler #14788
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<string>(formData.groupby); | ||
const [col] = groupby; | ||
const [currentSuggestionSearch, setCurrentSuggestionSearch] = useState(''); | ||
const [dataMask, dispatchDataMask] = useReducer<DataMaskReducer>(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<string>(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<SelectValue>( | ||
!isDisabled && defaultValue?.length ? defaultValue : [], | ||
); | ||
Comment on lines
-111
to
-113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. state handling is now deferred to the application, i.e. filter state is only being set via |
||
const [currentSuggestionSearch, setCurrentSuggestionSearch] = useState(''); | ||
const [dataMask, dispatchDataMask] = useReducer<DataMaskReducer>( | ||
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]); | ||
|
||
Comment on lines
-157
to
-169
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
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([]); | ||
} | ||
}, [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); | ||
// 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); | ||
} | ||
}, [JSON.stringify(filterState.value)]); | ||
}, [ | ||
col, | ||
isDisabled, | ||
defaultToFirstItem, | ||
enableEmptyFilter, | ||
inverseSelection, | ||
]); | ||
|
||
useEffect(() => { | ||
setDataMask(dataMask); | ||
|
@@ -229,7 +214,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { | |
<StyledSelect | ||
allowClear={!enableEmptyFilter} | ||
// @ts-ignore | ||
value={values} | ||
value={filterState.value || []} | ||
disabled={isDisabled} | ||
showSearch={showSearch} | ||
mode={multiSelect ? 'multiple' : undefined} | ||
|
@@ -253,7 +238,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) { | |
); | ||
})} | ||
{currentSuggestionSearch && | ||
!ensureIsArray(values).some( | ||
!ensureIsArray(filterState.value).some( | ||
suggestion => suggestion === currentSuggestionSearch, | ||
) && ( | ||
<Option value={currentSuggestionSearch}> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was changed to always initialize
ownState
to make sure the column type mappings are available if thesearchAllOptions
option is enabled after initialization.