React.jsでFormを扱う

今回はReact.jsでのFormの扱いについて書きたいと思います。

例えばReact.jsでは↓のようなことをすると変更出来ないテキストフィールドになってしまいます。どうしてなのかという話ですね。

```html
// ダメ
<input type="text" value="initial value" />

// ダメ
<input type="text" value={this.state.textValue} />
```
http://jsfiddle.net/koba04/kb3gN/8198/

## Controlled Component

Controlled ComponentとはStateによって値を管理するComponentのことでテキストフィールドの場合はこのようになります。

```js
var Text = React.createClass({
  getInitialState() {
    return {
      textValue: "initial value"
    };
  },
  changeText(e) {
    this.setState({textValue: e.target.value});
  },
  render() {
    return (
      <div>
        <p>{this.state.textValue}</p>
        <input type="text" value={this.state.textValue} onChange={this.changeText} />
      </div>
    );
  }
});
```

value値をStateで管理しつつ、`onChange`で明示的に`setState`して更新してあげる必要があります。


## UnControlled Component

UnControlled Componentとは逆に値を管理しないComponentで、初期値を設定した場合は`defaultValue`に設置する必要があります。

この場合は↑のようにonChangeで常にStateに反映してもいいですし、反映したい時にDOMからvalueを取得することも出来ます。

```js
var LiveText = React.createClass({
  getInitialState() {
    return {
      textValue: "initial value"
    };
  },
  changeText(e) {
    this.setState({textValue: this.refs.inputText.getDOMNode().value });
  },
  render() {
    return (
      <div>
        <p>{this.state.textValue}</p>
        <input type="text" ref="inputText" defaultValue="initial value" />
        <button onClick={this.changeText}>change</button>
      </div>
    );
  }
});
```

## textarea

テキストエリアの場合は、テキストフィールドと同じようにvalueに値を指定します。
HTMLの時と同じように`<textarea>xxxx</textarea>`とするとそれは`defaultValue`として扱われます。

```js
var OreTextArea = React.createClass({
  getInitialState() {
    return {
      textAreaValue: "initial value"
    };
  },
  onChangeText(e) {
    this.setState({textAreaValue: e.target.value});
  },
  onClick() {
    this.setState({textAreaValue: this.refs.textArea.getDOMNode().value}); 
  },
  render() {
    return (
      <div>
        <div>{this.state.textAreaValue}</div>
        <div>
          <textarea value={this.state.textAreaValue} onChange={this.onChangeText} />
        </div>
        <div>
          <textarea ref="textArea">this is default value</textarea>
          <button onClick={this.onClick}>change</button>
        </div>
      </div>
    );
  }
});
```
http://jsfiddle.net/koba04/wkkvrh4m/2/


## select

セレクトボックスの場合もvalueに値を設定します。`multiple={true}`のPropを指定するとvalueに配列を指定できるようになり、複数要素を選択することができるようになります。


```js
var OreSelectBox = React.createClass({
  getDefaultProps() {
    return {
      answers: [1, 10, 100, 1000]
    };
  },
  getInitialState() {
    return {
      selectValue: 1,
      selectValues: [1,100]
    };
  },
  onChangeSelectValue(e) {
    this.setState({selectValue: e.target.value});
  },
  // 他に良い方法があるかもい...
  onChangeSelectValues(e) {
    var values = _.chain(e.target.options)
      .filter(function(option) { return option.selected })
      .map(function(option) { return +option.value })
      .value()
    ;
    this.setState({selectValues: values});
  },
  render() {
    var options = this.props.answers.map(function(answer) {
      return <option value={answer} key={answer}>{answer}</option>;
    });
    return (
      <div>
        <div>selectValue: {this.state.selectValue}</div>
        <div>
          <select value={this.state.selectValue} onChange={this.onChangeSelectValue}>
            {options}
          </select>
        </div>
        <div>selectValues: {this.state.selectValues.join(",")}</div> 
        <div>
          <select multiple={true} defaultValue={this.state.selectValues} onChange={this.onChangeSelectValues}>
            {options}
          </select>
        </div>     
      </div>
    );
  }
});
```
http://jsfiddle.net/koba04/khdftsuu/


## LinkedStateMixin

LinkedStateMixinというaddonを使うとこんな感じで`onChange`を実装しなくてもstateに反映することも出来ます。
CheckBoxに対して使用する場合は、`checkedLink`となります。

```js
var React = require('react/addons');
var LinkedStateMixin = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  getInitialState() {
    return {
      textValue: "initial value"
    }
  },
  render() {
    return (
      <div>
        <div>value: {this.state.textValue}</div>
        <input type="text" valueLink={this.linkState('textValue')} />
      </div>
    );
  }
});
```

このMixinがやっていることは簡単ですし、中を見るきっかけとして追ってみるのも面白いかと思います。

### 蛇足(LinkedStateMixinの挙動がどうなってるのか確認してみる)

* まずMixinされる`linkState`を見てみると、ReactLinkというオブジェクトをvalueと何か作成されたSetterが渡されて作成しているのがわかります。

```js
  linkState: function(key) {
    return new ReactLink(
      this.state[key],
      ReactStateSetters.createStateKeySetter(this, key)
    );
  }
```
>>
https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/addons/link/LinkedStateMixin.js#L31-L36


* `ReactStateSetters.createStateKeySetter`の中を見てみると、渡したStateのkeyに対して`setState`する関数を作成しています。

```js
  createStateKeySetter: function(component, key) {
    // Memoize the setters.
    var cache = component.__keySetters || (component.__keySetters = {});
    return cache[key] || (cache[key] = createStateKeySetter(component, key));
  }
};

function createStateKeySetter(component, key) {
  // Partial state is allocated outside of the function closure so it can be
  // reused with every call, avoiding memory allocation when this function
  // is called.
  var partialState = {};
  return function stateKeySetter(value) {
    partialState[key] = value;
    component.setState(partialState);
  };
}
```
>>
https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/core/ReactStateSetters.js#L45-L61


* ReactLinkのConstructorでは値と`requestChange`として`createStateKeySetter`で作成した関数を保持されています。

```js
function ReactLink(value, requestChange) {
  this.value = value;
  this.requestChange = requestChange;
}
```
>>
https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/addons/link/ReactLink.js#L44-L47


* ここで、ReactLinkを渡している`valueLink`のPropに注目してみると、`requestChange`は`e.target.value`が渡されて呼ばれることがわかります。

```js
function _handleLinkedValueChange(e) {
  /*jshint validthis:true */
  this.props.valueLink.requestChange(e.target.value);
}

/**
  * @param {SyntheticEvent} e change event to handle
  */
function _handleLinkedCheckChange(e) {
  /*jshint validthis:true */
  this.props.checkedLink.requestChange(e.target.checked);
}
```
>>
https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/browser/ui/dom/components/LinkedValueUtils.js#L58-L69


* InputのComponentを見てみると、onChangeイベントで`valueLink`があれば、`_handleLinkedValueChange`を呼ばれて結果`setState`されることがわかるかと思います。

```js
  getOnChange: function(input) {
    if (input.props.valueLink) {
      _assertValueLink(input);
      return _handleLinkedValueChange;
    } else if (input.props.checkedLink) {
      _assertCheckedLink(input);
      return _handleLinkedCheckChange;
    }
    return input.props.onChange;
  }
```
>>
https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/browser/ui/dom/components/LinkedValueUtils.js#L140-L149

```js
  _handleChange: function(event) {
    var returnValue;
    var onChange = LinkedValueUtils.getOnChange(this);
    if (onChange) {
      returnValue = onChange.call(this, event);
    }
```
https://github.com/facebook/react/blob/3aa56039c60add45eb30f1edbaf40ddf195c54ce/src/browser/ui/dom/components/ReactDOMInput.js#L114-L119

-----

最後はちょっと脱線しましたが、今回はFormの操作について書きました。

明日はReact.jsのVirtualDOM実装で重要な役割を担っている`key`属性について書きたいと思います。