🍭

SWR と React Select でオートコンプリート機能付きプルダウンを実装する

2024/07/02に公開

React Select の Async コンポーネントを利用してオートコンプリート機能付きのプルダウンを実装する場合に SWR のキャッシュ機能を利用する方法を紹介します。

SWR を利用することで、プルダウンコンポーネントをページ内で複数利用した場合でも、無駄なHTTPリクエストが発生することがないため、パフォーマンスの向上やサーバーの負荷低減が期待できます。

ライブラリのインストール

npm 経由で react-selectswr をインストールします。

$ npm i react-select swr

SWR で取得したデータをプルダウンで表示する

次のコードがSWR で取得したデータをプルダウンで表示する機能を実装したコードになります。
例として、カラーを入力するための ColorSelect コンポーネントを実装します。

import AsyncSelect from 'react-select/async';
import useSWR from 'swr';
import type { GroupBase, OptionsOrGroups, Props } from 'react-select';
import { useCallback, useEffect, useState } from 'react';

export interface ColourOption {
  readonly value: string;
  readonly label: string;
}

export const colourOptions: readonly ColourOption[] = [
  { value: 'ocean', label: 'Ocean' },
  { value: 'blue', label: 'Blue' },
  { value: 'purple', label: 'Purple' },
  { value: 'red', label: 'Red' },
  { value: 'orange', label: 'Orange' },
  { value: 'yellow', label: 'Yellow' },
  { value: 'green', label: 'Green' },
  { value: 'forest', label: 'Forest' },
  { value: 'slate', label: 'Slate' },
  { value: 'silver', label: 'Silver' },
];

/**
 * サーバーから非同期でオプションを取得するモック関数
 */
function fetchColorOptions(inputValue: string) {
  return new Promise<ColourOption[]>((resolve) => {
    setTimeout(() => {
      const options = colourOptions.filter((option) =>
        option.label.toLowerCase().includes(inputValue.toLowerCase())
      );
      resolve(options);
    }, 1000);
  });
}

async function fetcher({ inputValue }: { inputValue: string }) {
  const options = await fetchColorOptions(inputValue);
  return options;
}

function ColorSelect() {
  const [inputValue, setInputValue] = useState<string>('');
  const { data: options, error } = useSWR({ inputValue }, fetcher);

  const loadOptions = useCallback(
    (
      _: string,
      callback: (options: OptionsOrGroups<unknown, GroupBase<unknown>>) => void
    ): void => {
      if (error) {
        callback([]);
        return;
      }

      if (options === undefined) {
        callback([]);
        return;
      }
      callback(options);
      return;
    },
    [options, error]
  );

  const handleInputChange = useCallback(
    (newValue: string) => {
      setInputValue(newValue);
    },
    [setInputValue]
  );

  return (
    <div className="container">
      <AsyncSelect
        isClearable
        isSearchable
        cacheOptions
        defaultOptions={options}
        loadOptions={loadOptions}
        onInputChange={handleInputChange}
      />
    </div>
  );
}

export default ColorSelect;

fetchColorOptions をサーバーからオプションを取得する処理に置き換えることでページ内で ColorSelect コンポーネントを複数利用した場合でも、1度目のリクエスト移行は SWR のキャッシュからプルダウンのデータを取得できます。

import ColorSelect from 'path/to/color-select'

export default function Page() {
  return (
    <form>
          <ColorSelect />
     <ColorSelect />
     <ColorSelect />
     <ColorSelect />
     <ColorSelect />
    </form>
  );
}

loadOptions の callback 引数を利用する

ポイントは loadOptionscallback 引数を利用している点です。 callback 引数を利用することで、 loadOptions 関数の中でデータを取得せず、 loadOptions 関数外の SWR で管理しているデータをプルダウン用のデータとして利用することができます。

SWR のデータは inputValue を Reactの State として管理することで、inputValue に変更がある場合に SWR のデータを更新しています。

inputValue は React Select の onInputChange props に更新用のコールバック関数を設定することで、ユーザーが入力を行うたびに inputValue を更新しています。

まとめ

今回は React Select と SWR を組み合わせてオートコンプリート機能付きプルダウンコンポーネントを実装する方法を紹介しました。

SWR を利用することでコンポーネントを複数利用した場合に、無駄なHTTPリクエストを削減することができます。既存の React Select で実装しているコンポーネントのパフォーマンス改善として、ぜひ取り入れてみてください。

Discussion