Falcor+Reactフルスタック（formsy-react）

[Meteor and Reactによるリアクティブシステム「ラーメン野郎を追いかけろ！ ＠ Twitter」を作ってみた](http://qiita.com/sand/items/02219ec5e42cc406ee5c "Meteor and Reactによるリアクティブシステム「ラーメン野郎を追いかけろ！ ＠ Twitter」を作ってみた")

[Falcor+Reactフルスタック（開発環境）](http://qiita.com/sand/items/06571033f99b55fffcb2 "Falcor+Reactのフルスタック（開発環境）")
[Falcor+Reactフルスタック（react-router）](http://qiita.com/sand/items/0a42ada13712abe87711 "Falcor+Reactのフルスタック（react-router）")
[Falcor+Reactフルスタック（views層とcomponents層）](http://qiita.com/sand/items/a969dd786b78554cf9fd "Falcor+Reactのフルスタック（views層とcomponents層）")
[Falcor+Reactフルスタック（formsy-react）](http://qiita.com/sand/items/4466b970f251445e13a4 "Falcor+Reactのフルスタック（formsy-react）")
[Falcor+Reactフルスタック（エラー処理）](http://qiita.com/sand/items/1ef8a19b15ac71c2fb99 "Falcor+Reactのフルスタック（エラー処理）")
[Falcor+Reactフルスタック（Material-UI）](http://qiita.com/sand/items/73d4aea4ae2697bfba2b "Falcor+Reactのフルスタック（Material-UI）")


　今回はReactのinputフォームのライブラリ（builder and validator）であるformsy-reactについて書きたいと思います。前回と同じユーザ登録画面を用いて説明したいと思います。components層のRegisterForm.jsを以下に示します。
https://github.com/christianalfoni/formsy-react

```js:src/components/RegisterForm.js
import React from 'react';
import Formsy from 'formsy-react';
import { RaisedButton, Paper } from 'material-ui';
import DefaultInput from './DefaultInput';

export class RegisterForm extends React.Component {
  constructor(props) {
    super(props);
    this.enableButton = this.enableButton.bind(this);
    this.disableButton = this.disableButton.bind(this);
    this.state = {
      canSubmit: false
    }; 
  }

  enableButton() {
    this.setState({
      canSubmit: true
    });
  }

  disableButton() {
    this.setState({
        canSubmit: false
    });
  }

  render() {
    return (
      <Formsy.Form onSubmit={this.props.onSubmit} onValid={this.enableButton} onInvalid={this.disableButton}>
        <Paper zDepth={1} style={{padding: 32}}>
          <h3>ユーザ登録フォーム</h3>
          <DefaultInput name='username' title='ユーザ名' value="" required />
          <DefaultInput name='firstName' title='名前' value="" required />
          <DefaultInput name='lastName' title='苗字' value="" required />
          <DefaultInput name='email' validations='isEmail' validationError='This is not a valid email' title='メール' value="" required />
          <DefaultInput type='password' name='password' title='パスワード' value="" required />
          <div style={{marginTop: 24}}>
            <RaisedButton
              disabled={!this.state.canSubmit}
              secondary={true}
              type="submit"
              style={{margin: '0 auto', display: 'block', width: 150}}
              label={'登録'} />
          </div>
        </Paper>
    </Formsy.Form>
    );
  }
}
```

　ここではMaterial-UIのcomponentであるRaisedButton と Paperを使っていますが、見た目を整えているだけなので説明は省きます。以下のサイトの実例でご確認ください。
http://www.mypress.jp:3019/

　formsy-reactはinputの入力値の整合性をチェックするために使われます。まず、上の例のFormsy.Formの２つのハンドラに注目します。

```
onValid={this.enableButton}
onInvalid={this.disableButton}
```

　後でDefaultInputで説明しますが、inputの入力値はキーボード入力がある度にチェックされます。全てのinputの入力値が正しくなったらonValidハンドラのenableButton()が呼ばれ、canSubmitがtrueになります。どれかひとつのinputの入力値が正しくない限りは、onInvalidハンドラのdisableButton()が呼ばれ、canSubmitがfalseになります。

```
            <RaisedButton
            disabled={!this.state.canSubmit}
```

　submitのRaisedButtonはcanSubmitの値がtrueの時のみ有効になります。それ以外は無効でボタンをクリックできない状態です。


　それではinput入力の正誤の定義はどこで行っているのでしょう？ まずは全てのDefaultInputに関してrequiredが付いているので、何らかの入力値が無ければ誤、何かしら入力があれば正と判断されます。
　次にemailのinputに関しては特にvalidations='isEmail'が指定されていますので、入力がメールフォーマットでないと誤と判断されます。validationErrorはエラー時のメッセージですが、DefaultInputでその使い方を説明します。

　次にDefaultInputを見てみましょう


```js:src/components/DefaultInput.js
import React from 'react';
import {TextField} from 'material-ui';
import {HOC} from 'formsy-react';

import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();

class DefaultInput extends React.Component {
  constructor(props) {
    super(props);
    this.changeValue = this.changeValue.bind(this); // changeValueの中でthis.propsを使う
  }

  changeValue(e) {
    this.props.setValue(e.currentTarget.value);
  }

  render() {
    const errorMessage = this.props.getErrorMessage();

    return (
      <div>
        <TextField
          ref={this.props.name}
          floatingLabelText={this.props.title}
          name={this.props.name}
          onChange={this.changeValue}
          required={this.props.required}
          type={this.props.type}
          value={this.props.getValue()}
          defaultValue={this.defaultValue} />
          <span>{errorMessage}</span>
     </div>
    );
  }

};

export default HOC(DefaultInput);
```


　次の２行はバグフィックス（？）のため必要なようです。
https://github.com/callemall/material-ui/issues/4670

```
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
```

　最後の行ですが、FormsyのHOC はHigher-Order Componentsの略で、Formsyの提供する関数にthis.props.getValue()、this.props.settValue()、this.props.getErrorMessage()などとアクセスできるようになります。React.createClassでクラスを作成する場合はFormsy.Mixinを使います。公式サイトでは主にこの方法でサンプルが記述されていますのでご注意ください。

changeValue()でキーボード入力毎の処理を行っています。

　getErrorMessage()は親componentのRegisterFormで定義したvalidationsが評価されて、エラーがある場合はvalidationErrorのメッセージを取得します。エラーが無ければnullが取得されます。エラーメッセージは以下のようにinputエレメントの後ろに表示されます。

```
<span>{errorMessage}</span>
```

　ログイン画面もformsyに関しては同じ構造ですのでここでは述べません。

　今回はMaterial-UIに関してはスルーしましたが、formsy-material-uiというラッパーが公式サイトで案内されています。時間があれば触ってみたいですが、今回はパスします。
https://github.com/rojobuffalo/formsy-material-ui