react.jsでautocompleteをやってみる


## モチベーション

autocompleteをするときに、jQueryのライリブラリは豊富にあります。react.jsでautocompleteを行うライブラリは幾つか存在して、jQueryのライブラリを使っているものもあります。けれども、できればjQueryを使わずにreact.jsだけでautocompleteを使いたいです。

## ezequiel/react-typeahead-component

react.jsだけでautocompleteを実現しているライブラリは幾つかありますが、今回は[ezequiel/react-typeahead-component](https://github.com/ezequiel/react-typeahead-component)を使いました。

特徴として次のものがあります(プロジェクトページからの引用)。

- Accessibility.
- BiDi support for RTL languages.
- Keyboard navigation.
- Hinting support for the current input value.
- Autocompletion of the hint when possible.
- Custom templates for each option.
- Auto-closing dropdown for the options list.
- 5KB minified+gzipped!

必要十分そうな気がします。他のライブラリを十分調べたわけではないのですが、他のライブラリも同等だと思います。

## サンプル

プロジェクトの中にfluxを使ったサンプルコードもあり、わかりやすいです。

## コード例

今回はfluxを使わずに、autocompleteを使って実装してみます。作るものは、ユーザの名前をサジェストしてくれるシステムです。コードはES6の記法で書きます。


```js
import React from 'react';
import Typeahead from 'react-typeahead-component';
import request from 'superagent';
import {throttle} from 'lodash';
import MyOptionTemplate from './my-option-template.jsx';

export default class UserSearch extends React.Component {
  constructor() {
    super();
    this.state = {
      initValue: '',
      options: []
    }
    this.getOptions = throttle(this.getOptionsFromApi, 300);
  }

  render() {
    return (
      <Typeahead
        placeholder="ユーザ検索"
        inputValue={this.state.inputValue}
        options={this.state.options}
        onChange={this.handleChange.bind(this)}
        optionTemplate={MyOptionTemplate}
        onOptionChange={this.handleOptionChange.bind(this)}
      />
    );
  }

  handleChange(evt) {
    let value = evt.target.value;
    this.setState({inputValue: value});
    this.getOptions(value);
  }

  getOptionsFromApi(value) {
    reqeust.get("/api/suggest")
      .query({q: value})
      .end(
        (err, res) => {
          let list = JSON.parse(res.text);
          this.setState({options: list});
        }
      )
  }

  handleOptionChange(evt, option) {
    this.setState({inputValue: option});
  }

  handleOptionClick(evt, option) {
    this.setState({inputValue: option});
  }

}

```

stateで管理するのは、inputタグの値を保持するinputValueとautocompleteで補完する候補を保持するoptionsの二つです。optionsは文字列の配列です。

throttleでオプションを取得するのは300msecごとに設定しています。一文字入力するごとに即座に候補を取得すると、サーバに負荷がかかりすぎる為の処置です。

Typeaheadでは多くのpropsを取りますが、最低限必要そうなのが、inputValue, options, onChange, optionTemplate, onOptionChangedです。inputValueは入力エリアの値、optionsは候補リストです。onChangeは入力エリアの値が変更した時に呼び出されます。値が変更したら、getOptionFromApiでサーバに候補一覧をajaxで取得します。取得したものは、stateに保持します。
onOptionChangeとonOptionClickは候補リストから選択した時に呼びだされます。通常はstateのinputValueをセットするだけです。

さて、optionTemplateですが、候補リストの表示形式を変更します。デフォルトのままだとあまりにも悲しいので、変更するのが良いでしょう。でも、今回はめんどくさいのでspanタグで囲むだけです。
ユーザ検索なので、検索候補にアバターのような写真のようなものも表示するものいいいですね。

```js
import React from 'react';


export default class MyOptionTemplate extends React.Component {
  render() {
    return (
      <div className="userSearchOptions">
        {this.renderOptions()}
      </div>
    );
  }

  renderOptions() {
    let optionData = this.props.data;
    let inputValue = this.props.userInputValue;
    if (optionData) {
      return (
        <span>
          {optionData}
        </span>
      )
    }
  }
}
```

ついでに、CSSも

```css



.react-typeahead-input-container {
    border: 1px solid #ccc;
    box-shadow: inset 0 1px 2px #eee;
}

.react-typeahead-input-container input {
    font-size: 16px;
    height: 27px;
    padding: 2px 6px;
    width: calc(100% - 12px);
    border: 0;
    outline: none;
}

.react-typeahead-input-container input:focus {
    border: 1px solid #1c62b9;
    box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
}

.react-typeahead-options {
    margin: 0;
    padding: 0;
    list-style-type: none;
    border: 1px solid #ccc;
    border-top: 0;
    cursor: default;
    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
    z-index: 200;
}

.react-typeahead-options li {
  padding: 5px;
}

```

クラス名から多分、それぞれ何をしているか想像がつくと思います。