react-selectで全選択とか独自UIの実装 & filterOptionのコントロール

# react-selectで全選択の実装 

react-selectで独自のボタンと埋め込む時の備忘録

## こんなのをしたい

全選択とか全解除で複数選択を楽させたい

![スクリーンショット 2020-10-12 18.38.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131778/e2ee1a73-34bc-da44-ad94-a7dd59b3254f.png)

## 成果物

[codesandbox](https://codesandbox.io/s/bold-platform-zf8r3?file=/src/App.js:0-2128)

<details><summary>ソース</summary><div>

```tsx

import React, { useState } from "react";
import "./styles.css";
import ReactSelect, { components, createFilter } from "react-select";

const options = [
  { value: "ocean1", label: "Ocean", color: "#00B8D9" },
  { value: "blue", label: "Blue", color: "#0052CC" },
  { value: "purple", label: "Purple", color: "#5243AA" },
  { value: "red", label: "Red", color: "#FF5630" },
  { value: "orange", label: "Orange", color: "#FF8B00" },
  { value: "yellow", label: "Yellow", color: "#FFC400" },
  { value: "green", label: "Green", color: "#36B37E" },
  { value: "forest", label: "Forest", color: "#00875A" },
  { value: "slate", label: "Slate", color: "#253858" },
  { value: "silver", label: "Silver", color: "#666666" }
];

const widget = {
  label: "Hoge",
  value: "*"
};

export default function App() {
  const [selected, setSelected] = useState([]);

  const Option = (props) => {
    //  値が*の時は独自のボタンとかを描画
    if (props.data.value === "*") {
      return (
        <div style={{ display: "flex", justifyContent: "space-around" }}>
          <button
            onClick={() => {
              setSelected(options);
            }}
          >
            全選択
          </button>
          <button
            onClick={() => {
              setSelected([]);
            }}
          >
            全解除
          </button>
        </div>
      );
    }

    return <components.Option {...props} />;
  };

  // 独自UIはfIlterの対象外に
  const withOutWidgetFilter = (candidate, input) => {
    if (input && candidate.value === "*") {
      return true;
    }

    return createFilter(null)(candidate, input);
  };

  return (
    <div className="App">
      <h1>React Select With Widgets Example</h1>

      <div style={{ width: "500px", margin: "auto" }}>
        <ReactSelect
          value={selected}
          options={[widget, ...options]}
          components={{ Option }}
          isMulti
          closeMenuOnSelect={false}
          hideSelectedOptions={false}
          filterOption={withOutWidgetFilter}
          onChange={(value) => {
            setSelected(value);
          }}
        />
      </div>
    </div>
  );
}

```

</div></details>

## 解説

肝としては、optionsを拡張して独自の機能を埋め込んでいる

```tsx
  const Option = (props) => {
    // 値が*の時は独自のボタンとかを描画
    if (props.data.value === "*") {
      return (
        <div style={{ display: "flex", justifyContent: "space-around" }}>
          <button
            onClick={() => {
              setSelected(options);
            }}
          >
            全選択
          </button>
          <button
            onClick={() => {
              setSelected([]);
            }}
          >
            全解除
          </button>
        </div>
      );
    }

    return <components.Option {...props} />;
  };
```

react-selectの絞り込み機能で埋め込んだUIを絞り込みの対象にしたないときはこうする

```tsx
  const withOutWidgetFilter = (candidate, input) => {
    // 独自UI用の値の時はtrueを返しfilteringの対象外に
    if (input && candidate.value === "*") {
      return true;
    }

    // デフォルトの絞り込み処理
    return createFilter(null)(candidate, input);
  };
```

ざっくりとこんな感じで実現可能です
絞り込んだ値に対しての全選択とかはもう少し処理が必要ですが、ベースはこんな感じです



## おまけ 

Filterで絞り込まれている値ははSelectのrefから参照できます

```tsx
const ref = React.useRef(null);

// 絞り込まれた値が取得できる
const filtedOptions = ref.current?.select.state.menuOptions.render || []

return <Select
        ref={ref}
        {...props}
       />
```