モチベーション
autocompleteをするときに、jQueryのライリブラリは豊富にあります。react.jsでautocompleteを行うライブラリは幾つか存在して、jQueryのライブラリを使っているものもあります。けれども、できればjQueryを使わずにreact.jsだけでautocompleteを使いたいです。
ezequiel/react-typeahead-component
react.jsだけでautocompleteを実現しているライブラリは幾つかありますが、今回は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の記法で書きます。
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タグで囲むだけです。
ユーザ検索なので、検索候補にアバターのような写真のようなものも表示するものいいいですね。
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も
.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;
}
クラス名から多分、それぞれ何をしているか想像がつくと思います。