Skip to content

Commit d595d7e

Browse files
authored
fix(combobox): cache management for option values and labels (#698)
1 parent 0cdb148 commit d595d7e

File tree

1 file changed

+66
-48
lines changed

1 file changed

+66
-48
lines changed

packages/combobox/src/useCombobox.ts

Lines changed: 66 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ export const useCombobox = <
6666
altKey?: boolean;
6767
}
6868

69+
interface ICacheState {
70+
values: OptionValue[];
71+
labels: Record<string, string>;
72+
selectedValues: OptionValue[];
73+
disabledValues: OptionValue[];
74+
hiddenValues: OptionValue[];
75+
}
76+
6977
const [triggerContainsInput, setTriggerContainsInput] = useState<boolean>();
7078
const [downshiftInputValue, setDownshiftInputValue] = useState(inputValue);
7179
const [matchValue, setMatchValue] = useState('');
@@ -83,42 +91,44 @@ export const useCombobox = <
8391
getOptionId: (index: number, isDisabled?: boolean, isHidden?: boolean) =>
8492
`${prefix}--option${isDisabled ? '-disabled' : ''}${isHidden ? '-hidden' : ''}-${index}`
8593
});
86-
const labels: Record<string, string> = useMemo(() => ({}), []);
87-
const selectedValues: OptionValue[] = useMemo(() => [], []);
88-
const disabledValues: OptionValue[] = useMemo(() => [], []);
89-
const hiddenValues: OptionValue[] = useMemo(() => [], []);
90-
const values = useMemo(() => {
91-
const retVal: OptionValue[] = [];
94+
const cache = useMemo(() => {
95+
const retVal: ICacheState = {
96+
values: [],
97+
labels: {},
98+
selectedValues: [],
99+
disabledValues: [],
100+
hiddenValues: []
101+
};
92102
const setValues = (option: IOption) => {
93103
if (option.disabled || option.hidden) {
94-
if (option.disabled && !disabledValues.includes(option.value)) {
95-
disabledValues.push(option.value);
104+
if (option.disabled && !retVal.disabledValues.includes(option.value)) {
105+
retVal.disabledValues.push(option.value);
96106
}
97107

98-
if (option.hidden && !hiddenValues.includes(option.value)) {
99-
hiddenValues.push(option.value);
108+
if (option.hidden && !retVal.hiddenValues.includes(option.value)) {
109+
retVal.hiddenValues.push(option.value);
100110
}
101111
} else {
102-
retVal.push(option.value);
112+
retVal.values.push(option.value);
103113

104-
const disabledIndex = disabledValues.indexOf(option.value);
114+
const disabledIndex = retVal.disabledValues.indexOf(option.value);
105115

106116
if (disabledIndex !== -1) {
107-
disabledValues.splice(disabledIndex, 1);
117+
retVal.disabledValues.splice(disabledIndex, 1);
108118
}
109119

110-
const hiddenIndex = hiddenValues.indexOf(option.value);
120+
const hiddenIndex = retVal.hiddenValues.indexOf(option.value);
111121

112122
if (hiddenIndex !== -1) {
113-
hiddenValues.splice(hiddenIndex, 1);
123+
retVal.hiddenValues.splice(hiddenIndex, 1);
114124
}
115125
}
116126

117-
if (option.selected && !selectedValues.includes(option.value)) {
118-
selectedValues.push(option.value);
127+
if (option.selected && !retVal.selectedValues.includes(option.value)) {
128+
retVal.selectedValues.push(option.value);
119129
}
120130

121-
labels[option.value] = option.label || option.value;
131+
retVal.labels[option.value] = option.label || option.value;
122132
};
123133

124134
options.forEach(option => {
@@ -130,11 +140,11 @@ export const useCombobox = <
130140
});
131141

132142
return retVal;
133-
}, [options, disabledValues, hiddenValues, selectedValues, labels]);
134-
const initialSelectionValue = isMultiselectable ? selectedValues : selectedValues[0];
143+
}, [options]);
144+
const initialSelectionValue = isMultiselectable ? cache.selectedValues : cache.selectedValues[0];
135145
const initialInputValue = isMultiselectable
136146
? ''
137-
: toLabel(labels, initialSelectionValue as string);
147+
: toLabel(cache.labels, initialSelectionValue as string);
138148
const _defaultActiveIndex = useMemo(() => {
139149
if (defaultActiveIndex === undefined) {
140150
return isAutocomplete && isEditable ? 0 : undefined;
@@ -155,7 +165,7 @@ export const useCombobox = <
155165
*/
156166

157167
if (selectionValue === undefined || selectionValue === null) {
158-
if (!isMultiselectable && selectedValues.length > 1) {
168+
if (!isMultiselectable && cache.selectedValues.length > 1) {
159169
throw new Error('Error: expected useCombobox `options` to have no more than one selected.');
160170
}
161171
}
@@ -240,7 +250,7 @@ export const useCombobox = <
240250
// Fix Downshift standard last option activation on listbox
241251
// expansion. Addresses problems with initial multiselectable and
242252
// overeager `defaultActiveIndex` comboboxes.
243-
changes.highlightedIndex = values.length - 1;
253+
changes.highlightedIndex = cache.values.length - 1;
244254
}
245255

246256
break;
@@ -297,7 +307,7 @@ export const useCombobox = <
297307
return changes;
298308
};
299309

300-
const transformValue = (value: OptionValue | null) => (value ? toLabel(labels, value) : '');
310+
const transformValue = (value: OptionValue | null) => (value ? toLabel(cache.labels, value) : '');
301311

302312
/** Hooks */
303313

@@ -318,7 +328,7 @@ export const useCombobox = <
318328
toggleButtonId: idRef.current.trigger,
319329
menuId: idRef.current.listbox,
320330
getItemId: idRef.current.getOptionId,
321-
items: values,
331+
items: cache.values,
322332
inputValue: downshiftInputValue,
323333
initialInputValue,
324334
itemToString: transformValue as any /* HACK around Downshift's generic type overuse */,
@@ -394,7 +404,7 @@ export const useCombobox = <
394404
_selectionValue.length - 1 // multiselectable most recent
395405
]
396406
: _selectionValue;
397-
const index = values.findIndex(current => current === value);
407+
const index = cache.values.findIndex(current => current === value);
398408

399409
if (index !== -1) {
400410
setActiveIndex(index);
@@ -410,7 +420,7 @@ export const useCombobox = <
410420
_isExpanded,
411421
_selectionValue,
412422
_inputValue,
413-
values,
423+
cache.values,
414424
_defaultActiveIndex,
415425
setActiveIndex
416426
]
@@ -522,7 +532,7 @@ export const useCombobox = <
522532
event.preventDefault();
523533

524534
if (_activeIndex !== -1) {
525-
setDownshiftSelection(values[_activeIndex]);
535+
setDownshiftSelection(cache.values[_activeIndex]);
526536
}
527537

528538
if (!isMultiselectable) {
@@ -551,15 +561,17 @@ export const useCombobox = <
551561
: _selectionValue;
552562

553563
if (offsetValue !== null) {
554-
offset = values.findIndex(current => current === offsetValue);
564+
offset = cache.values.findIndex(current => current === offsetValue);
555565
}
556566
}
557567

558-
for (let index = 0; index < values.length; index++) {
559-
const valueIndex = (index + offset) % values.length;
560-
const value = values[valueIndex];
568+
for (let index = 0; index < cache.values.length; index++) {
569+
const valueIndex = (index + offset) % cache.values.length;
570+
const value = cache.values[valueIndex];
561571

562-
if (toLabel(labels, value).toLowerCase().startsWith(_matchValue.toLowerCase())) {
572+
if (
573+
toLabel(cache.labels, value).toLowerCase().startsWith(_matchValue.toLowerCase())
574+
) {
563575
setActiveIndex(valueIndex);
564576
break;
565577
}
@@ -596,8 +608,8 @@ export const useCombobox = <
596608
setActiveIndex,
597609
setDownshiftSelection,
598610
matchValue,
599-
values,
600-
labels,
611+
cache.values,
612+
cache.labels,
601613
triggerContainsInput,
602614
isAutocomplete,
603615
isEditable,
@@ -840,7 +852,7 @@ export const useCombobox = <
840852
'aria-selected': ariaSelected,
841853
id: option
842854
? idRef.current.getOptionId(
843-
hiddenValues.indexOf(option.value),
855+
cache.hiddenValues.indexOf(option.value),
844856
option.disabled,
845857
option.hidden
846858
)
@@ -858,7 +870,7 @@ export const useCombobox = <
858870
'aria-selected': ariaSelected,
859871
id: option
860872
? idRef.current.getOptionId(
861-
disabledValues.indexOf(option.value),
873+
cache.disabledValues.indexOf(option.value),
862874
option.disabled,
863875
option.hidden
864876
)
@@ -870,14 +882,20 @@ export const useCombobox = <
870882

871883
return getDownshiftOptionProps<any>({
872884
item: option.value,
873-
index: values.indexOf(option.value),
885+
index: cache.values.indexOf(option.value),
874886
'aria-disabled': undefined,
875887
'aria-hidden': undefined,
876888
'aria-selected': ariaSelected,
877889
...optionProps
878890
} as IDownshiftOptionProps<OptionValue>);
879891
},
880-
[getDownshiftOptionProps, disabledValues, hiddenValues, values, _selectionValue]
892+
[
893+
getDownshiftOptionProps,
894+
cache.disabledValues,
895+
cache.hiddenValues,
896+
cache.values,
897+
_selectionValue
898+
]
881899
);
882900

883901
const getMessageProps = useCallback<IUseComboboxReturnValue['getMessageProps']>(
@@ -914,21 +932,21 @@ export const useCombobox = <
914932
if (Array.isArray(_selectionValue)) {
915933
return _selectionValue.map(value => ({
916934
value,
917-
label: labels[value],
918-
disabled: disabledValues.includes(value),
919-
hidden: hiddenValues.includes(value)
935+
label: cache.labels[value],
936+
disabled: cache.disabledValues.includes(value),
937+
hidden: cache.hiddenValues.includes(value)
920938
}));
921939
} else if (_selectionValue) {
922940
return {
923941
value: _selectionValue,
924-
label: toLabel(labels, _selectionValue),
925-
disabled: disabledValues.includes(_selectionValue),
926-
hidden: hiddenValues.includes(_selectionValue)
942+
label: toLabel(cache.labels, _selectionValue),
943+
disabled: cache.disabledValues.includes(_selectionValue),
944+
hidden: cache.hiddenValues.includes(_selectionValue)
927945
};
928946
}
929947

930948
return null;
931-
}, [_selectionValue, disabledValues, hiddenValues, labels]);
949+
}, [_selectionValue, cache.disabledValues, cache.hiddenValues, cache.labels]);
932950

933951
return useMemo<IUseComboboxReturnValue>(
934952
() => ({
@@ -945,13 +963,13 @@ export const useCombobox = <
945963
/* state */
946964
selection,
947965
isExpanded: _isExpanded,
948-
activeValue: values[_activeIndex],
966+
activeValue: cache.values[_activeIndex],
949967
inputValue: _inputValue,
950968
/* actions */
951969
removeSelection
952970
}),
953971
[
954-
values,
972+
cache.values,
955973
selection,
956974
_isExpanded,
957975
_activeIndex,

0 commit comments

Comments
 (0)