即使重新渲染,React 也不会在状态更改时更新 DOM

时间:2021-03-28 23:13:53

标签: reactjs

我正在尝试实现一个多选下拉菜单,用户可以在其中选择下拉菜单中的多个项目。我似乎无法让它在选择项目时工作,它会在视觉上显示(在 DOM 上)。

enter image description here

我创建了一个 working code repository here,但有问题的组件在下面。如果您克隆链接的存储库,您会立即明白我的意思:

  1. 打开下拉菜单
  2. 选择 1 个或多个项目。预期的效果是这些选定的项目现在具有蓝色背景,就像它们悬停时一样
  3. 关闭下拉菜单并重新打开它,您会看到所选项目现在突出显示,但我希望无需关闭并重新打开下拉菜单即可实现这一点。
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;

1 个答案:

答案 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
    ]);
  }
};