From 6c49b3b97762fadfb1f48ff4c3d6632bc9f75d92 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 30 Apr 2024 15:40:12 -0400 Subject: [PATCH] fix: Fix issue where SelectField was not populating selection into textfield (#1017) --- src/inputs/internal/ComboBoxBase.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/inputs/internal/ComboBoxBase.tsx b/src/inputs/internal/ComboBoxBase.tsx index b1ae6d4bd..119704c9b 100644 --- a/src/inputs/internal/ComboBoxBase.tsx +++ b/src/inputs/internal/ComboBoxBase.tsx @@ -12,6 +12,7 @@ import { keyToValue, Value, valueToKey } from "src/inputs/Value"; import { BeamFocusableProps } from "src/interfaces"; import { getFieldWidth } from "src/inputs/utils"; import { useDebounce } from "use-debounce"; +import equal from "fast-deep-equal"; /** Base props for either `SelectField` or `MultiSelectField`. */ export interface ComboBoxBaseProps extends BeamFocusableProps, PresentationFieldProps { @@ -130,8 +131,18 @@ export function ComboBoxBase(props: ComboBoxBaseProps) const values = useMemo(() => propValues ?? [], [propValues]); + const selectedOptionsRef = useRef(options.filter((o) => values.includes(getOptionValue(o)))); const selectedOptions = useMemo(() => { - return options.filter((o) => values.includes(getOptionValue(o))); + // `selectedOptions` should only ever update if the `values` prop actually change. + // Assuming `values` is a state variable, then it should hold its identity until it _really_ changes. + // Though, it is possible that the `options` prop has changed, which is a dependency on this `useMemo`. + // That could trigger an unnecessary new reference for `selectedOptions`, and cause additional renders or unexpected state changes. + // We should avoid updating `selectedOptions` unless `values` has actually changed. + if (!equal(values.sort(), selectedOptionsRef.current.map(getOptionValue).sort())) { + selectedOptionsRef.current = options.filter((o) => values.includes(getOptionValue(o))); + } + + return selectedOptionsRef.current; }, [options, values, getOptionValue]); const { contains } = useFilter({ sensitivity: "base" }); @@ -277,7 +288,6 @@ export function ComboBoxBase(props: ComboBoxBaseProps) // Reset inputValue when closed or selected changes useEffect(() => { - if (debouncedSearch) return; if (state.isOpen && multiselect) { // While the multiselect is open, let the user keep typing setFieldState((prevState) => ({ @@ -292,7 +302,7 @@ export function ComboBoxBase(props: ComboBoxBaseProps) inputValue: getInputValue(selectedOptions, getOptionLabel, multiselect, nothingSelectedText, isReadOnly), })); } - }, [state.isOpen, selectedOptions, getOptionLabel, multiselect, nothingSelectedText, isReadOnly, debouncedSearch]); + }, [state.isOpen, selectedOptions, getOptionLabel, multiselect, nothingSelectedText, isReadOnly]); // Call on search callback when the user types in the input field useEffect(() => {