【React】セレクトボックスを選択したあとにフォーカスを検索ボックスに移す

## はじめに
セレクトボックスで選択したタイプに応じたキーワードを検索するようなUIを構築する際に、選択ボックスを選択した後に自動的に検索ボックスにフォーカスを移動することで、ユーザー体験を向上させることができるかと思います。
この記事では、ReactとHeadless UIライブラリ`@radix-ui/react-select`を使った実装方法を順を追ってざっくりと紹介します。

https://ui.shadcn.com/docs/components/select

## Step 1: 必要なコンポーネントをインポートする

最初に、必要なコンポーネントをインポートします。今回はSelect(セレクトボックス)関連とInputWithSearchIcon(検索窓)の2つのコンポーネントを使用します。

```jsx
import { InputWithSearchIcon } from '@/components/Elements/InputWithSearchIcon';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/Elements/Select';
```

## Step 2: SelectボックスとInputボックスを準備する

次に、SelectボックスとInputボックスを準備します。これらはHTMLの基本的な要素で、Selectボックスでは選択肢をユーザーに提示し、Inputボックスではユーザーからの入力を受け付けます。

```jsx
const HeaderSearch: React.FC = () => {
  const searchTypes = [
    { value: 'lang', label: '言語' },
    { value: 'framework', label: 'フレームワーク' },
    //...(略)
  ];

  return (
    <div className='ml-8 flex'>
      <Select>
        <SelectTrigger className='w-auto'>
          <SelectValue placeholder='言語' />
        </SelectTrigger>
        <SelectContent className='bg-white'>
          {searchTypes.map((searchType) => (
            <SelectItem
              className='focus:bg-slate-100'
              key={searchType.value}
              value={searchType.value}
            >
              {searchType.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
      <div>
        <InputWithSearchIcon
          className='w-64'
          id='keyword'
          type='text'
          name='keyword'
          placeholder='キーワードで検索'
        />
      </div>
    </div>
  );
};
```


- イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1040483/ecc756ae-4140-7e8c-1e7e-e90af8f9129b.png)


## Step 3: Inputボックスにrefを設定する

Inputボックスにフォーカスを当てるためには、その要素の参照を取得する必要があります。Reactではこれを実現するためにrefという機能を提供しています。

```jsx
import { useRef } from 'react';

// 中略

// InputWithSearchIconの参照を作成
const inputRef = useRef<HTMLInputElement>(null);

// 中略

<InputWithSearchIcon
  ref={inputRef}
  // 中略
/>
```

## Step 4: Selectボックスで選択肢が選ばれた時にフォーカスを移動する

最後に、Selectボックスで選択肢が選ばれた時にフォーカスをInputボックスに移動させる処理を追加します。
今回はRadix UIのSelectコンポーネントを使用しているので`onOpenChange`(通常はonSelectイベント等を使用)イベントを利用します。

- onOpenChange
    - セレクトボックスの開閉状態が変更されたときに呼び出されるRadix UIが用意してくれているコールバック関数

```jsx
// ユーザーがセレクトボックスから項目を選択した後、検索ボックスにフォーカスする
const handleSelect = () => {
  // 選択ボックスが閉じるのと同時にフォーカスを移すと、検索ボックスからすぐにフォーカスを失うため、setTimeoutを遅延させる
  setTimeout(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, 0);
};

// 中略

<Select onOpenChange={handleSelect}>
  // 中略
</Select>
```


これで、Selectボックスで選択肢を選ぶと、自動的にInputボックスにフォーカスが移動します。

- Demo
[![Image from Gyazo](https://i.gyazo.com/750b3349982a553a54a8c69bc20a12fa.gif)](https://gyazo.com/750b3349982a553a54a8c69bc20a12fa)

### 補足: 遅延フォーカスについて理解する

選択ボックスが閉じる瞬間にフォーカスを移すと、ブラウザの動作によりすぐに検索ボックスからフォーカスが失われてしまう現象が発生するようで、これはブラウザがフォーカスを持つ要素を一度しか変更できないため。
つまり、選択ボックスが閉じられた時点でブラウザはフォーカスを外部に移し、その直後に我々が検索ボックスにフォーカスを設定しようとしても、それが無視されてしまうようです。

この問題を解決するために、JavaScriptの`setTimeout`関数を使って、選択ボックスの閉じる動作が完全に終了してからフォーカスを移動させます。
`setTimeout`関数は指定した時間（ミリ秒）後に指定した関数を実行しますが、今回は遅延時間として`0`を指定しました。
これは、指定した関数を「可能な限り早く、しかし現在実行中の他のコードが全て完了した後に」実行することを意味します。

- 以下のコードは、選択ボックスが閉じた後に、可能な限り早く（しかし他のコードが全て実行された後に）検索ボックスにフォーカスを移すように設定しています。

```jsx
const handleSelect = () => {
  // 選択ボックスが閉じるのと同時にフォーカスを移すと、検索ボックスからすぐにフォーカスを失うため、setTimeoutを遅延させる
  setTimeout(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, 0);
};
```

このように、setTimeoutを用いることで、非同期的な処理や微細なタイミングの調整を行うことが可能です。

## 最後に
今回実は、Tailwindを使用していてなぜかセレクトボックスを選択した際に、セレクトボックスにフォーカスが当たって色付きの枠線が表示されてしまう問題がどうしても解消できず、今回のような「選択したらフォーカスを検索窓に移動する」という対策を取りましたが、結果的にユーザー体験がよくなると思ったので一石二鳥な気分でしたｗ

## 参考

https://www.radix-ui.com/