Выбрать все / Отменить выбор всех опций в реакции-выбора

Возможно ли иметь опцию "Выбрать все / Отменить все" в реакции выбора? Это то, что встроено, или я должен сделать это сам?

5 ответов

В компании, где я работаю, мы сделали обертку вокруг react-selectс некоторыми дополнительными функциями и стилем. Одна из функций - Выбрать / Отменить выбор всех.

Вот ссылка на страницу, демонстрирующую компонент:

https://topia.design/components/multi-select/

Вот подход, который я использовал для реализации Select / Unselect All:

https://codesandbox.io/s/distracted-panini-8458i?file=/src/MultiSelect.js

import React, { useRef } from "react";
import ReactSelect from "react-select";

export const MultiSelect = props => {
  // isOptionSelected sees previous props.value after onChange
  const valueRef = useRef(props.value);
  valueRef.current = props.value;

  const selectAllOption = {
    value: "<SELECT_ALL>",
    label: "All Items"
  };

  const isSelectAllSelected = () =>
    valueRef.current.length === props.options.length;

  const isOptionSelected = option =>
    valueRef.current.some(({ value }) => value === option.value) ||
    isSelectAllSelected();

  const getOptions = () => [selectAllOption, ...props.options];

  const getValue = () =>
    isSelectAllSelected() ? [selectAllOption] : props.value;

  const onChange = (newValue, actionMeta) => {
    const { action, option, removedValue } = actionMeta;

    if (action === "select-option" && option.value === selectAllOption.value) {
      props.onChange(props.options, actionMeta);
    } else if (
      (action === "deselect-option" &&
        option.value === selectAllOption.value) ||
      (action === "remove-value" &&
        removedValue.value === selectAllOption.value)
    ) {
      props.onChange([], actionMeta);
    } else if (
      actionMeta.action === "deselect-option" &&
      isSelectAllSelected()
    ) {
      props.onChange(
        props.options.filter(({ value }) => value !== option.value),
        actionMeta
      );
    } else {
      props.onChange(newValue || [], actionMeta);
    }
  };

  return (
    <ReactSelect
      isOptionSelected={isOptionSelected}
      options={getOptions()}
      value={getValue()}
      onChange={onChange}
      hideSelectedOptions={false}
      closeMenuOnSelect={false}
      isMulti
    />
  );
};

Я вдохновился от метода Алекса, но я изменил какой-то раздел его кода. Вот пример, который я подготовил, если вам все еще нужно, вы можете проверить.

Вы можете легко сделать это следующим образом:

https://medium.com/@alex_escalante/react-select-alloptionoptions-with-a-single-click-1ebf5a33fe31

Для всех, кто заинтересован: Вдохновленный ответом @Mihhail Lapushkin, я создал типизированную версию, используя Next.js и Typescript. React-Select немного привередлив к SSR и SSG, поэтому ему требуются дополнительные идентификаторы. См. живую демонстрацию в CodeSandBox или наGitHub Многоразовый компонент выглядит примерно так:

      import { ActionMeta, Options } from 'react-select';
import { Dispatch, SetStateAction, useRef } from "react";

import Select from 'react-select';
import makeAnimated from 'react-select/animated';

const animate = makeAnimated();

interface Props {
  options: Option[]
  selected: Option[],
  setSelected: Dispatch<SetStateAction<any>>,
  title: string,
  hide?: boolean
}

export type Option = {
  value: number | string;
  label: string;
};

/* Advanced Dropdown Select Menu using React select
 * Inspired by https://stackoverflow.com/a/61250357/20007391 and 
 * added typing.
 * 
 * For more information about the react-select API, options, and 
 * customization, see https://react-select.com/home
 * 
 * This component is just the select box. Options are passed in from 
 * the parent and the parent maintains the state of the object for 
 * callbacks and function handling. */
export default function MultiSelect(props: Props) {
  // For component "memory"
  const valueRef = useRef(props.selected);
  valueRef.current = props.selected;
  
  const selectAllOption = {value: "*", label:"Select All"};
  const isSelectAllSelected = () => valueRef.current.length === props.options.length;
  const isOptionSelected = (option: Option, selectValue: Options<Option>) => 
    valueRef.current.some(({value}) => value === option.value) ||
    isSelectAllSelected();

  //const getOptions = () => isSelectAllSelected() ? [] : [selectAllOption, ...props.options];
  const getOptions = () => [selectAllOption, ...props.options];
  const getValue = () => isSelectAllSelected() ? [selectAllOption] : props.selected;

  const handleSelect = (newValue: unknown, actionMeta: ActionMeta<unknown>) => {
    const {action, option, removedValue} = actionMeta;
    // Reassigning for typing. Unknown by default
    const opt = option as Option;
    const removed = removedValue as Option;
    if (action === "select-option" && opt.value === selectAllOption.value) {
      //console.log("new item selected");
      props.setSelected(props.options);
    } else if ((action === "deselect-option" && opt.value === selectAllOption.value) || (action === "remove-value" && removed.value === selectAllOption.value)) {
      props.setSelected([]);
      //console.log("all items removed");
    } else if (actionMeta.action === "deselect-option" && isSelectAllSelected()) {
      props.setSelected(
        props.options.filter(({ value }) => value !== opt.value));
        //console.log("new item removed");
    } else {
      props.setSelected(newValue || []);
      //console.log("new item added");
    }
    //console.log(action, newValue, getValue());
  }

  return (
      <Select
        isOptionSelected={isOptionSelected}
        closeMenuOnSelect={false}
        defaultValue={getOptions()} // Should default to select all option
        value={getValue()}
        isMulti
        //isSearchable
        //components={animate}
        placeholder={props.title}
        options={getOptions()}
        onChange={handleSelect}
        hideSelectedOptions={props.hide ?? false}
        instanceId={props.title}
        id={props.title}
      />
  )
}

Единственная липкая вещь связана с определением того, как выглядят ваши варианты. Вы можете отформатировать свои параметры, чтобы они были одинаковыми, или определить общий и настроить защиту типа для проверки неизвестных значений (по умолчанию для ActionMeta<> и newValue в реакции-выборе). Ведение журнала консоли добавлено для удобства и демонстрационных целей.

Решение для react-select@5.2.0 (изящно обеспечивает опцию «выбрать все») (удалить части TypeScript для запуска в чистом jS)

      const MultiSelect: React.FC<MultiSelectProps> = ({
  options,
  defaultValue,
  onChange,
}) => {
  const selectAllOption = { label: 'select all', value: '*' };

  const initialVisibleOptions =
    options.length === defaultValue?.length
      ? options
      : [...options, selectAllOption];

  const [availableOptions, setAvailableOptions] = useState<Option[]>(
    initialVisibleOptions,
  );

  const [selectedValues, setSelectedValues] = useState<readonly Option[]>(
    defaultValue,
  );

  const relayOnChange = (newSelectedOptions: readonly Option[]) => {
    const selectAllIsSelected = !!newSelectedOptions.find(
      o => o.value === selectAllOption.value,
    );

    const newComponentState = selectAllIsSelected
      ? {
          selectedValues: options,
          availableOptions: [],
        }
      : {
          selectedValues: newSelectedOptions,
          availableOptions: initialVisibleOptions,
        };

    setSelectedValues(newComponentState.selectedValues);
    setAvailableOptions(newComponentState.availableOptions);
    onChange(newComponentState.selectedValues);
  };

  return (
    <Select
      isMulti
      options={availableOptions}
      value={selectedValues}
      defaultValue={selectedValues}
      onChange={relayOnChange}
    />
  );
};

Другие вопросы по тегам