👆

react-selectのカスタマイズでつまづいた点まとめ(style中心)

2023/10/10に公開

概要

react-selectでは画像を選択肢に入れたセレクトボックスを作成することが可能です。
しかし、一部分でしかreact-selectを使用しない場合、react-selectを使用した箇所とそうでない箇所とで見た目の統一感を出す必要があるケースが存在するかと思います。
その時にstyleのカスタマイズでいくつかつまづいた点があり、それぞれの解決法や解決にあたって役立った記事などをまとめました。

react-selectカスタマイズの基本

基本的なカスタマイズについては公式情報および、waddy_uさんの以下の記事を参考にさせていただきました。
https://react-select.com/home
https://zenn.dev/waddy/articles/react-select-customize

カスタマイズ可能な箇所の名称

カスタマイズ可能な箇所なのか?やその箇所の名称が分からず、困ることが数回ありました。
はてぶでtmegosさんが書かれていた、style keyの一覧およびそれを分かりやすく表現したサンプルコードを参考にさせていただきました。
https://tmegos.hatenablog.jp/entry/react-select-style-object
https://codesandbox.io/s/vjvn91qx97?file=/src/index.js

画像を選択肢に表示する

waddy_uさんがこちらで書かれています。
https://zenn.dev/waddy/articles/react-select-customize#選択肢にアバター画像を表示する

return (
  <Select
+    formatOptionLabel={(option) => <FormatOptionLabel option={option} />}
  />
);

とした上で、別途FormatOptionLabelの中身を書くことで、好きなように画像を表示できます。

今回自分の関わる案件では、とにかくシンプルな画像のみを表示するセレクトボックスが必要だったため、以下のようなコードを書きました。Next.jsを使用している環境だったため、imgではなくImageを使用しています。またTypescriptのため型の指定をしています。

// eslint-disable-next-line react/display-name
export const FormatOptionLabel = memo(({ option }: { option: sampleParam }) => (
  <Image
    src={`/imgs/sample${option.value}.png`}
    alt={option.value}
    width={300}
    height={30}
    referrerPolicy='no-referrer'
  />
))

※脱線ですが、Next.jsでImageを使用する場合、画像ファイルはpublicフォルダの下に置く必要があります。
今回はimgsフォルダを作成し、public/imgs/sample0.pngというように、option.valueに対応するファイル名の画像を作成しました。

あの縦線を消す方法

react-selectのデフォルトのstyleで特徴的な、あの縦線を消す方法です。
あの縦線の画像
あの縦線(赤枠部分)

あの縦線はstyle keyでindicatorSeparatorと呼ばれます。
https://zenn.dev/waddy/articles/react-select-customize#セパレーターを消す
でwaddy_uさんも書かれていますが、

return (
  <Select
+   components={{
+     IndicatorSeparator: () => null,
+   }}
  />
);

で消すことができます。

styleをどこで書いていくか?

ガラッと全体を書き換えたいケースと、基本はreact-selectのデフォルトを使用したいケースと、色々あると思います。
react-selectでは、

以下に実際に試した3つのパターンをまとめました。stylesを使う手法(1・3)とclassName,classNamesを使う手法(2・4)を組み合わせることも可能です。
自分の場合は最終的に、1と4の組み合わせで使用しました(基本は1で完結させるつもりでしたが、isFocusedの指定でtailwindの記法を使用したかったため)。

1:あちこち編集したい/定数で別途指定する

return(
  <Select
+    styles={customStyles}
  />
);

で書いていくことができます。
以下はそのカスタム例です。

reactSelectSample.tsx
import Select from 'react-select';

const customStyles = {
  control: (provided, state) => ({
    ...provided,
    width: '100%',
    borderRadius: 'none',
    border: '1px solid gray',
    backgroundColor: '#eee',
    textAlign: 'center',
    '&:hover': {
      border: '2px solid red',
      cursor: 'pointer',
    },
  }),
  option: (provided, state) => ({
    ...provided,
    backgroundColor: state.isSelected ? '#f00' : '#eee',
    color: state.isSelected ? 'white' : 'black',
    '&:hover': {
      backgroundColor: '#f88',
    },
  }),
  menu: (provided) => ({
    ...provided,
    backgroundColor: '#eee',
  }),
  singleValue: (provided, state) => ({
    ...provided,
    color: state.isSelected ? 'white' : 'black',
  }),
};
~~~(中略)~~~
<Select styles={customStyles} />

2:あちこち編集したい/別のCSSに切り出す

この方法ではTailwind CSSなどを組み合わせることが可能です。

reactSelectSample.tsx
import Select from 'react-select';
import 'tailwindcss/tailwind.css';
import './customStyles.css';
~~~(中略)~~~
<Select className="select-box" />
customStyles.css
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

.select-box {
  @apply w-full rounded-none border-2;

  & .Select__control {
    @apply rounded-none;
  }

  & .Select__option {
    @apply bg-white text-black;
  }

  & .Select__option--is-selected {
    @apply bg-blue-500 text-white;
  }

  & .Select__option--is-focused {
    @apply bg-gray-100;
  }

  & .Select__single-value {
    @apply text-black;
  }

  & .Select__indicator-separator {
    @apply hidden;
  }

  & .Select__dropdown-indicator {
    @apply text-black;
  }

  & .Select__placeholder {
    @apply text-gray-500;
  }
}

3:少しだけ変えたい/Selectのstylesに直書き

基本はbaseStylesを踏襲し、細かなところだけ変更する際に使える方法です。

reactSelectSample.tsx
<Select
  styles={{
    control: (baseStyles) => ({
      ...baseStyles,
      minWidth: 160,
    }),
  }}
/>

4:少しだけ変えたい/SelectのclassNamesに直書き

この方法ではTailwind CSSなどを組み合わせることが可能です。
以下の例ではセレクトボックスがフォーカスされた時に枠線で囲まれるようにしました。

reactSelectSample.tsx
import Select from 'react-select';
import 'tailwindcss/tailwind.css';
~~~(中略)~~~
<Select
  classNames={{
                 control: (state) => (state.isFocused ? 'outline-none ring-2 ring-blue-500' : ''),
              }}
/>

optionsの定義の仕方

react-selectのoptionsvaluelabelの二つの値が必須となっています。
以下に簡単なサンプルとして2つの方法を書きました。

1:基本は定数として別途定義する

const options = [
  { value: 'option1', label: 'Option 1' },
  { value: 'option2', label: 'Option 2' },
  { value: 'option3', label: 'Option 3' }
];
<Select options={options} />

2:map()する

export const SAMPLE_TYPES = ['Apple', 'Banana', 'Cherry']
<Select
  value={{ label: sample.value, value: sample.value }}
  options={SAMPLE_TYPES.map((i) => ({
            label: i.toString(),
            value: i.toString(),
          }))}
/>

最後に

react-selectは見た目も内容も細かくカスタマイズできるため便利ですが、一方で見た目のカスタマイズに関して中々細かな情報までは見つけにくく、触ってみると大変なところもありました。
自分自身はこの記事で触れた参考元の情報と、styleの書き換えをどこで行うべきなのか?が分かってから大分スムーズにやりたいことができるようになった印象です。
この記事が何かの参考になれば幸いです。

Discussion