我正在尝试实现一个多选下拉菜单,用户可以在其中选择下拉菜单中的多个项目。我似乎无法让它在选择项目时工作,它会在视觉上显示(在 DOM 上)。
我创建了一个 working code repository here,但有问题的组件在下面。如果您克隆链接的存储库,您会立即明白我的意思:
const MultiSelectMenu: React.FC<MultiSelectMenuProps> = ({ choices }) => {
const [selectedChoices, setSelectedChoices] = useState<SelectChoice[]>([]);
const [showDropdown, setShowDropdown] = useState(false);
const selectMenuNode = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleMouseDown(event: MouseEvent) {
if (
!(selectMenuNode.current as HTMLDivElement).contains(
event.target as Node
)
) {
setShowDropdown(false);
}
}
document.addEventListener("mousedown", handleMouseDown);
return () => {
// Unbind the event listener on clean up
document.removeEventListener("mousedown", handleMouseDown);
};
}, [selectMenuNode]);
const choiceIsSelected = (choice: SelectChoice): boolean => {
return (
selectedChoices.filter((selected) => selected.id === choice.id).length > 0
);
};
const onSelectChoice = (selectedChoice: SelectChoice) => {
if (choiceIsSelected(selectedChoice)) {
const arr = selectedChoices;
arr.splice(arr.indexOf(selectedChoice), 1);
setSelectedChoices(arr);
} else {
const arr = selectedChoices;
arr.push(selectedChoice);
setSelectedChoices(arr);
}
};
const selectMenuItems = choices.map((value) => {
if (value.id === "figma") {
console.log(`Figman is selected: ${choiceIsSelected(value)}`);
}
return (
<SelectMenuItem
choice={{ display: value.display, id: value.id }}
onSelectChoice={onSelectChoice}
isSelected={choiceIsSelected(value)}
key={value.id}
/>
);
});
const focusStyles =
"focus:outline-none focus:border-primary focus:ring-2 focus:ring-secondary";
console.log("Render");
return (
<div ref={selectMenuNode}>
<label
id="listbox-label"
className="block text-sm font-medium text-gray-700"
>
Multi-Select
</label>
<div className="mt-1 relative">
<button
type="button"
aria-haspopup="listbox"
aria-expanded="true"
aria-labelledby="listbox-label"
className={`bg-white relative w-full border border-gray rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-pointer ${focusStyles} sm:text-sm`}
onClick={() => setShowDropdown(!showDropdown)}
>
<span className="block truncate">
{selectedChoices.length > 0
? selectedChoices.length
: "Select one..."}
</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
// {showDropdown ? ()
</span>
</button>
{showDropdown && (
<div className="absolute mt-2 w-full rounded-md bg-white shadow-lg">
<ul
tabIndex={-1}
role="listbox"
aria-labelledby="listbox-label"
aria-activedescendant="listbox-item-3"
className="max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
>
{selectMenuItems}
</ul>
</div>
)}
</div>
</div>
);
};
export default MultiSelectMenu;
答案 0 :(得分:1)
您正在改变状态对象。
const onSelectChoice = (selectedChoice: SelectChoice) => {
if (choiceIsSelected(selectedChoice)) {
const arr = selectedChoices;
arr.splice(arr.indexOf(selectedChoice), 1); // <-- state mutation!
setSelectedChoices(arr);
} else {
const arr = selectedChoices;
arr.push(selectedChoice); // <-- state mutation!
setSelectedChoices(arr);
}
};
你不应该直接改变状态对象,总是返回一个新的对象/数组引用。移除元素时过滤当前状态数组,添加元素时浅拷贝并追加新的选择。
const onSelectChoice = (selectedChoice: SelectChoice) => {
if (selectedChoices.some(choice => choice.id === selectedChoice.id)) {
setSelectedChoices(selectedChoices => selectedChoices.filter(
choice => choice.id !== selectedChoice.id,
));
} else {
setSelectedChoices(selectedChoices => [
...selectedChoices,
selectedChoice
]);
}
};