Selecting and de-selecting same item "onSelectedItemChange" not fired
Hmoulvad opened this issue · comments
downshift
version: 7.6.2node
version: 18.18.0npm
(oryarn
) version: 9.8.1
import { useCombobox } from 'downshift';
import { useEffect, useMemo, useState } from 'react';
import ChevronDown from '~/icons/iconography/chevron/down.svg';
import ClearIcon from '~/icons/iconography/close.svg';
import LoopIcon from '~/icons/search.svg';
import { DropdownItem } from './models';
import {
ClearButton,
Input,
InputContainer,
Item,
ItemCheckbox,
ItemCount,
ItemLabel,
ItemLeftContainer,
List,
SearchFilter,
StyledButton,
} from './styled';
type MultipleSelectorProps = {
options: DropdownItem[];
initialSelectedItems?: string[];
label: string;
noLabel?: boolean;
id: string;
onSelectedItemsChange?: (items: DropdownItem[]) => void;
};
const MultipleSelector = ({
options,
label,
id,
initialSelectedItems,
noLabel = false,
onSelectedItemsChange,
}: MultipleSelectorProps) => {
const [inputValue, setInputValue] = useState<string>('');
const [selectedItems, setSelectedItems] = useState<DropdownItem[]>(
options.filter((item) => initialSelectedItems?.includes(item.value)),
);
const items = useMemo(() => {
return options.filter((item) =>
item.label.toLowerCase().includes(inputValue?.toLowerCase() ?? ''),
);
}, [options, inputValue]);
const {
getItemProps,
getMenuProps,
getToggleButtonProps,
getLabelProps,
getInputProps,
highlightedIndex,
isOpen,
} = useCombobox({
items,
itemToString: (item) => item?.label ?? '',
id,
inputValue,
stateReducer: ({ inputValue, highlightedIndex }, { changes, type }) => {
switch (type) {
case useCombobox.stateChangeTypes.ItemClick:
case useCombobox.stateChangeTypes.InputKeyDownEnter:
return {
...changes,
inputValue,
isOpen: true,
highlightedIndex,
};
default:
return changes;
}
},
onSelectedItemChange: ({ selectedItem }) => {
if (!selectedItem) {
return;
}
const itemIsDeselected = selectedItems.some(
(item) => item.value === selectedItem.value,
);
const newSelectedItemList = itemIsDeselected
? selectedItems.filter((item) => item.value !== selectedItem.value)
: [...selectedItems, selectedItem];
setSelectedItems(newSelectedItemList);
onSelectedItemsChange?.(newSelectedItemList);
},
});
// Update selectedItems state on props change
useEffect(() => {
setSelectedItems(items.filter((item) => initialSelectedItems?.includes(item.value)));
}, [initialSelectedItems, items]);
const isListHidden = !isOpen && !noLabel;
const isSearchFilterHidden = items.length < 8 || isListHidden;
return (
<>
<StyledButton
isOpen={isOpen}
noLabel={noLabel}
{...getToggleButtonProps()}
tabIndex={0}
>
<span {...getLabelProps()}>{label}</span>
<ChevronDown />
</StyledButton>
<List
aria-hidden={isListHidden}
isHidden={isListHidden}
noLabel={noLabel}
{...getMenuProps()}
>
<SearchFilter aria-hidden={isSearchFilterHidden} isHidden={isSearchFilterHidden}>
<InputContainer>
<LoopIcon />
<Input type="text" placeholder={`Søg i ${label}`} {...getInputProps()} />
{inputValue ? (
<ClearButton onClick={() => setInputValue('')}>
<ClearIcon />
</ClearButton>
) : null}
</InputContainer>
</SearchFilter>
{items.map((item, index) => (
<Item
highlighted={highlightedIndex === index}
key={item.label}
{...getItemProps({
item,
index,
})}
>
<ItemLeftContainer>
<ItemCheckbox
type="checkbox"
checked={selectedItems.some(
(selected) => selected.value === item.value,
)}
readOnly
value={item.value}
/>
<ItemLabel>{item.label}</ItemLabel>
</ItemLeftContainer>
{item.count ? <ItemCount>({item.count})</ItemCount> : null}
</Item>
))}
</List>
</>
);
};
export default MultipleSelector;
What you did:
I selected and option and tried to de-select it.
What happened:
The function callback "onSelectedItemChange" doesn't fire since there is no changes in selected item, but I want it to fire so I can de-select the item.
Help:
Does anyone have an idea to get around this... <3
Hi @Hmoulvad ! With https://github.com/downshift-js/downshift/tree/master/src/hooks/useCombobox#statereducer I believe is the easiest.
stateReducer(state, actionAndChanges) {
const {type, changes} = actionAndChanges
// this prevents the menu from being closed when the user selects an item with 'Enter' or mouse
switch (type) {
case useCombobox.stateChangeTypes.InputKeyDownEnter:
case useCombobox.stateChangeTypes.ItemClick: {
if (changes.selectedItem === state.selectedItem) {
return {...changes, selectedItem: null, inputValue: ''}
}
return changes
}
default:
return changes // otherwise business as usual.
}
},
Hi @Hmoulvad ! With https://github.com/downshift-js/downshift/tree/master/src/hooks/useCombobox#statereducer I believe is the easiest.
stateReducer(state, actionAndChanges) { const {type, changes} = actionAndChanges // this prevents the menu from being closed when the user selects an item with 'Enter' or mouse switch (type) { case useCombobox.stateChangeTypes.InputKeyDownEnter: case useCombobox.stateChangeTypes.ItemClick: { if (changes.selectedItem === state.selectedItem) { return {...changes, selectedItem: null, inputValue: ''} } return changes } default: return changes // otherwise business as usual. } },
Thanks will check it out!