OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

react-select:インクリメンタル検索で一致部分を強調表示

f:id:kd1:20210503152503p:plain

インクリメンタル検索で、入力と一致した部分文字列をハイライトする。
というのを、React Selectを使ってやる。
基本的には公式APIドキュメントを読んでいけば書いてあるはずの話だが、カスタムコンポーネントの部分はちょっとつまづいたのでメモ。

前提

  • react v17.0.2
  • react-select v4.3.0

React Select の基本的な使い方

インクリメンタル検索だけならあまり考えずに実装できる。
label , value(+その他必要なプロパティ)を持つオブジェクトをoptionとして渡すだけで、 label に対して入力文字列での検索ができる。

自前で絞り込みロジックを実装したければ、 filterOption プロパティが使える。その他いろいろ使えるプロパティはこちら参照。

import React from 'react';
import Select from "react-select";

export default function App() {
  const options = [
    { label: "JavaScript", value: 1 },
    { label: "TypeScript", value: 2 },
    { label: "ECMAScript", value: 3 },
    { label: "Java", value: 4 },
    { label: "JScript", value: 5 }
  ];

  return (
    <div>
      <Select options={options} />
    </div>
  );
}

optionコンポーネントを自前のものに差し替え

入力した文字列と一致した部分文字列を強調表示させる。

optionのCSSをいじるだけなら、 styles で指定できるが、それだけでは難しいので、optionコンポーネントを自前のカスタムコンポーネントで差し替える。

↓こんな感じで Option コンポーネントを作って components に指定する。

const Option = (props) => {
  return <div>...</div>
}

const options = [/*省略*/];

return (
  <div>
    <Select options={options} components={{ Option }} />
  </div>
);

ただ、一から実装するのはめんどくさいので、必要なところ以外はデフォルト部品の実装を使いたい。
components.Option でもとの部品が定義されているので、これを持ってくる。

//import Select, { components } from "react-select";

const Option = (props) => {
  //もとの部品をそのまま使った状態
  return <components.Option {...props} />
}

この components.Optionlabel プロパティの値を表示するようになっているが、実は children を取ることもできて、 children があればそっちを描画するようになっているっぽい。
なので、 children のところに表示したい内容を実装するだけで良い。

この Option が受け取る props の内容がドキュメントを見てもいまいち分からなかったのだけど、少なくとも

  • data : options で渡したobject
  • selectProps
    • inputValue : 入力された文字列

が入ってくることはわかった。
なので↓こんなふうに書ける。

const Option = (props) => {
  const input = props.selectProps.inputValue;
  const label = props.data.label;

  if (input === "") {
    return <components.Option {...props} />;
  }

  const idx = label.toLowerCase().indexOf(input.toLowerCase());

  const styles = {
    highlight: {
      fontWeight: "bold",
      color: "#ee0000"
    }
  };

  return (
    <components.Option {...props}>
      <div>
        <span>{label.slice(0, idx)}</span>
        <span style={styles.highlight}>{label.slice(idx, idx + input.length)}</span>
        <span>{label.slice(idx + input.length)}</span>
      </div>
    </components.Option>
  );
};

コード全体はこちら。