React Tutorial Example

項目が多いので　*React tutorial example series* に分けました。
モジュールのバーッションをアップデートして、内容も少しアップグレードしています。

1. [React Tutorial Example (Express)](http://qiita.com/ogomr/items/b90c68764c3c75cf501e)
* [React Tutorial Example (Hot Module Replacement)](http://qiita.com/ogomr/items/9eb64ff5f7d4fcd08514)
* [React Tutorial Example (Redux)](http://qiita.com/ogomr/items/64aae7c8d6a113608230)
* [React Tutorial Example (ECMAScript 2015)](http://qiita.com/ogomr/items/3f0d7a35d5d0184db019)
* [React Tutorial Example (Convention)](http://qiita.com/ogomr/items/e6e702633c4f49a1893a)
* [React Tutorial Example (PostCSS)](http://qiita.com/ogomr/items/1f8f18a18ac9c2e79179)
* [React Tutorial Example (Package)](http://qiita.com/ogomr/items/224a1355078ea1a809a5)
* [React Tutorial Example (Jest)](http://qiita.com/ogomr/items/e0001c2744781f004873)
* [React Tutorial Example (Electron)](http://qiita.com/ogomr/items/681f5bffe9908e24469a)

----

**こちらはバージョンが古い記事です。**

[React](https://facebook.github.io/react/) は ユーザインターフェース を作成する **JavaScript** のライブラリです。
*Virtual DOM* や *Data Flow* が特徴で主に [Facebook](https://code.facebook.com/) で開発されています。

`v0.14` から **React** と **ReactDOM** の２つのパッケージに分割されました。
[React Tutorial](https://facebook.github.io/react/docs/tutorial.html) も ReactDOM のアップデートがされているので [Redux](http://rackt.github.io/redux/) や [Socket.IO](http://socket.io/) を利用してサンプルを実装しました。

実装した [React Tutorial Example](https://github.com/dddrb/react-tutorial-example) には6つの *Tag* でコードの差分を見ることができます。

* [v0.0.1](https://github.com/dddrb/react-tutorial-example/tree/v0.0.1): React Tutorial
* [v0.0.2](https://github.com/dddrb/react-tutorial-example/tree/v0.0.2): Webpack
* [v0.0.3](https://github.com/dddrb/react-tutorial-example/tree/v0.0.3): Redux Tutorial 1st
* [v0.0.4](https://github.com/dddrb/react-tutorial-example/tree/v0.0.4): Redux Tutorial 2nd
* [v0.0.5](https://github.com/dddrb/react-tutorial-example/tree/v0.0.5): WebSocket
* [v0.0.6](https://github.com/dddrb/react-tutorial-example/tree/v0.0.6): PropTypes

## Environment

環境は React `v0.14` がリリースされた時点の最新のバージョンで開発しています。

* node: v4.1.2
* npm: v2.14.4
* express: v4.13.1
* react: v0.14.0
* redux: v3.0.2
* socket.io: v1.3.7

----

## Express

Webサーバーには [Express](http://expressjs.com/) を利用しました。
**Express Generator** で簡単にWebサーバーが構築できます。

```bash
$ npm install express-generator -g
$ express react-tutorial-example
$ cd react-tutorial-example && npm install
$ DEBUG=react-tutorial-example:* npm start
```

![express](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/express.png)

ブラウザで `http://localhost:3000/` にアクセスしましょう。
*Welcome to Express* が表示されたら OK です。
次に進みましょう。

----

## Git

**SCM** は **Tutorial** の進みの差分を見直すことが出来て便利です。
今回は [20](https://github.com/dddrb/react-tutorial-example/commits/master) 程度の *Commit* で実装しました。

```bash
$ echo node_modules/ > .gitignore
$ git init
$ git add --all
$ git commit -m "Initial commit"
```

特に **Git** は **Tutorial** に必要ありません。

----

## React

![react](https://raw.githubusercontent.com/facebook/react/gh-pages/img/logo_small_2x.png)

### Tutorial

**React** で実際に *Comments Box* を作成しながら *Virtual DOM* や *Data Flow* を体験しましょう。
（[こちら](https://github.com/reactjs/react-tutorial)に Tutorial の Source があります。）

#### Style

* まずは **Express Generator** で作成した **Express** の *Stylesheet* を **Tutorial** の [Source](https://raw.githubusercontent.com/reactjs/react-tutorial/master/public/css/base.css) からコピーします。

```bash
$ curl https://raw.githubusercontent.com/reactjs/react-tutorial/master/public/css/base.css -o ./public/stylesheets/style.css
```

#### Layout

* **Express** の *Layout* も **Tutorial** の [Source](https://raw.githubusercontent.com/reactjs/react-tutorial/master/public/index.html) からコピーしたい所ですが、テンプレートに [Jade](http://jade-lang.com/) を利用しているのでHTMLを変換しました。
* `marked.min.js` は **Tutorial** の *Adding Markdown* で利用しますが、ここで追加しています。

`views/layout.jade`:

```
doctype html
html
  head
    meta(charset='utf-8')
    title React Tutorial
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.js')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.js')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js')
  body
    block content
```

* **Express** ではトップページのレイアウトを `index.jade` に記述します。
* しかし、 **React** は *Virtual DOM* なのでレイアウトに *ID* (`#content`) 以外のコードを記述しません。

`views/index.jade`:

```
extends layout

block content
  #content
  script(type='text/babel', src='/javascripts/example.js')
  script(type='text/babel').
```

#### Script

* **Tutorial** の `tutorial1.js` から `tutorial20.js` を `example.js` に記述しながら進みます。

`public/javascripts/example.js`:

```
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});

ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);
```

それでは **Tutorial** を始めましょう。

#### Tutorial 1

* [your-first-component](https://facebook.github.io/react/docs/tutorial.html#your-first-component) では *CommentBox* の **Component** が追加されます。

![tutorial_0-1](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_0-1.png)

いつもの *Hello, world!* で始まります。

#### Tutorial 2,3

* [composing-components](https://facebook.github.io/react/docs/tutorial.html#composing-components) では *CommentList* と *CommentForm* が構造で追加されます。

![tutorial_2-3](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_2-3.png)

ふたつの *Hello, world!* が表示されました。こうして **Component** ごとに分割してコードを記述します。

#### Tutorial 4,5

* [using-props](https://facebook.github.io/react/docs/tutorial.html#using-props) では *CommentList* に *Comment* が構造で追加されて、`props` で値が表示されています。

![tutorial_4-5](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_4-5.png)

データは上位の **Component** から下位の **Component** に流れます。

#### Tutorial 6,7

* [adding-markdown](https://facebook.github.io/react/docs/tutorial.html#adding-markdown) では *Comment* が Markdown に変換されます。

![tutorial_6-7](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_6-7.png)

`another` が *another* に変換されていますね。

#### Tutorial 8,9,10

* [hook-up-the-data-model](https://facebook.github.io/react/docs/tutorial.html#hook-up-the-data-model) では *CommentList* にデータモデルから値が渡されて表示されています。

![tutorial_8-10](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_8-10.png)

表示は変わっていませんが、性能が大きく向上しました。

#### Search Comments API

次の Tutorial から サーバーに API が必要なので
**Express** に `Comments` を取得する API を実装します。

* **Express** の便利な *Router* で `Comments` を実装します。

`app.js`:

```
var comments = require('./routes/api/comments');
app.use('/api/comments', comments);
```

* ファイルを読み込んで、レスポンスを作成します。

`routes/api/comments.js`:

```
var fs = require('fs');
var express = require('express');
var router = express.Router();

/* GET comments listing. */
router.get('/', function(req, res, next) {
  fs.readFile('db/comments.json', function(err, data) {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});

module.exports = router;
```

* データベースもどきは **JSON** にします。

`db/comments.json`:

```
[
  {"author": "Pete Hunt", "text": "This is one comment"},
  {"author": "Jordan Walke", "text": "This is *another* comment"}
]
```

JavaScript Only は素敵！

#### Tutorial 11 - 14

* [fetching-from-the-server](https://facebook.github.io/react/docs/tutorial.html#fetching-from-the-server) では *CommentList* に `GET /api/comments` から値が取得されて表示されます。

![tutorial_11-14](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_11-14.png)

`db/comments.json` の値を変更すると数秒後に値の表示が変わります。

#### Create Comment API

今度は Express に *Comment* を登録する API を実装します。

* 先ほどの Router に `router.post` を追加するだけで完成です。

`routes/api/comments.js`:

```
/* POST comment creating. */
router.post('/', function(req, res) {
  fs.readFile('db/comments.json', function(err, data) {
    var comments = JSON.parse(data);
    comments.push(req.body);
    fs.writeFile('db/comments.json', JSON.stringify(comments, null, 4), function(err) {
      res.setHeader('Cache-Control', 'no-cache');
      res.json(comments);
    });
  });
});
```

#### Tutorial 15 - 20

* [adding-new-comments](https://facebook.github.io/react/docs/tutorial.html#adding-new-comments) では *CommentForm* から *Comment* を Post します。

![tutorial_15-20](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_15-20.png)

送信するとデータベースもどきに `author` と `text` が保存されます。
これで **React** の *Comments Box* が完成しました。

----

## Webpack

<img src='https://raw.githubusercontent.com/webpack/webpack.github.com/master/assets/logo.png' alt='webpack logo' width='300'>

[Webpack](https://webpack.github.io/) は依存関係のモジュールを取込み、静的な *Assets* を生成するライブラリです。
今回は先ほどの Tutorial で利用したモジュールを Webpack から生成しましょう。

```bash
$ npm install webpack --save-dev
$ npm install webpack-dev-middleware --save-dev
$ npm install babel-loader --save-dev
$ npm install jquery --save
$ npm install react --save
$ npm install react-dom --save
```

* **Express** に **Webpack** の機能を追加します。

`app.js`:

```
var webpack = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackConfig = require('./config/webpack.config');

// webpack setup
var compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, {
  noInfo: true, publicPath: webpackConfig.output.publicPath
}));
```

* **Tutorial** で記述した `example.js` を `entry` に設定します。

`config/webpack.config.js`:

```
module.exports = {
  entry: [
    './public/javascripts/example.js'
  ],
  output: {
    path: __dirname + '/static/',
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: ['babel'],
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: ['', '.js']
  }
};
```

* `marked` 以外は **Webpack** で生成するので、`layout.jade` から除外します。

`views/layout.jade`:

```
doctype html
html
  head
    meta(charset='utf-8')
    title React Tutorial
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(src='https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js')
  body
    block content
```

* **Webpack** が生成する `bundle.js` を `script` で参照するように編集します。

`views/index.jade`:

```
extends layout

block content
  #content
  script(src='static/bundle.js')
```

* **JQuery** や **React** を `import` に記述します。

`public/javascripts/example.js`:

```
import $ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
```

次の **Redux** の **Tutorial** は **ECMAScript 6** で記述しています。

----

## Redux

<img src='https://raw.githubusercontent.com/reactjs/redux/master/logo/logo.png' alt='redux logo' width='100'>

[Redux](http://redux.js.org/) は [Flux](https://facebook.github.io/flux/) の Framework で主に *Action*, *Reducer*, *Store* で構成されます。

### Yeoman

Redux の [examples](https://github.com/rackt/redux/tree/master/examples) を参照するとディレクトリの構造にルールがあるのが解ります。

この **Redux Tutorial** は [Yeoman](http://yeoman.io/) の **Redux Generator** で出力された構造を参考にしています。
最初から **React Tutorial** を **Redux** で順に進めて行くので `yo redux` コマンドの実行は不要です！

```bash
$ npm install --global yo
$ npm install --global generator-redux
$ yo redux
```

* actions
    * *Store* にアプリケーションのデータを送信する情報が設置される。
* components
    * *Component*(構成部品) が設置される。
* constants
    * *Action* と *Reducer* の `const` が設置される。
* containers
    * `App.js` にアプリケーションの起点が設置される。
* reducers
    * *Action* に応じてアプリケーションの状態を変更する機構が設置される。
* store
    * アプリケーションの状態を保持する機構と状態を更新する機構が設置される。
* utils
    * *DevTools* などが設置される。

### Tutorial

#### Tutorial 1

* `app` ディレクトリに各役割のディレクトリを設置しました。

`app/index.js`:

```
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';

ReactDOM.render(
  <App />,
  document.getElementById('content')
);
```

* アプリケーションの起点の `App.js` を設置します。

`app/containers/App.js`:

```
import React from 'react';
import Home from '../components/Home';

export default React.createClass({
  render() {
    return (
      <div>
        <Home />
      </div>
    );
  }
});
```

* コンポーネントの起点の `Home.js` を設置します。

`app/components/Home.js`:

```
import React, {Component} from 'react';
import CommentBox from './CommentBox';

class Home extends Component {
  render() {
    return (
      <div>
        <CommentBox />
      </div>
    );
  }
}

export default Home
```

* コンポーネントに `CommentBox.js` を設置します。

`app/components/CommentBox.js`:

```
import React, {Component} from 'react';

class CommentBox extends Component {
  render() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
}

export default CommentBox;
```

* **Webpack** の `entry` を `index.js` に変更します。

`config/webpack.config.js`:

```
module.exports = {
  entry: [
    './app/index.js'
  ],
```

![tutorial_0-1](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_0-1.png)

再び *Hello, world!* が来ました。
まだ、**Redux** は利用していません。

#### Tutorial 2,3

* コンポーネントはそれぞれのファイルに設置します。

`app/components/CommentBox.js`:

```
import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';

class CommentBox extends Component {
  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
}

export default CommentBox;
```

* コンポーネントのファイルが Fat になるのを抑止されます。

`app/components/CommentList.js`:

```
import React, {Component} from 'react';

class CommentList extends Component {
  render() {
    return (
      <div className="commentList">
        Hello, world! I am a CommentList.
      </div>
    );
  }
}

export default CommentList;
```

* モジュールが個々にできるので、テストも小さくできます。

`app/components/CommentForm.js`:

```
import React, {Component} from 'react';

class CommentForm extends Component {
  render() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
}

export default CommentForm;
```

![tutorial_2-3](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_2-3.png)

ふたつの *Hello, world!* が表示されていますね。
しかしまだ、**Redux** は利用していません。

#### Tutorial 4,5

* 上位の *CommentList* から *Comment* にデータを流します。

`app/components/CommentList.js`:

```
import React, {Component} from 'react';
import Comment from './Comment';

class CommentList extends Component {
  render() {
    return (
      <div className="commentList">
        <Comment author="Pete Hunt">This is one comment</Comment>
        <Comment author="Jordan Walke">This is *another* comment</Comment>
      </div>
    );
  }
}

export default CommentList;
```

* 下位の `props` でデータを表示しています。

`app/components/Comment.js`:

```
import React, {Component} from 'react';

class Comment extends Component {
  render() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {this.props.children}
      </div>
    );
  }
}

export default Comment;
```

![tutorial_4-5](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_4-5.png)

ここでもまだ、**Redux** は利用していません。

#### Tutorial 6,7

* **ECMAScript 6** なので `function` の記述が不要です。

`app/components/Comment.js`:

```
import React, {Component} from 'react';

class Comment extends Component {
  rawMarkup() {
    let rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return { __html: rawMarkup };
  }

  render() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
}

export default Comment;
```

![tutorial_6-7](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_6-7.png)

もちろん **Redux** は利用していません。

#### Tutorial 8,9,10

* データを *Home* から下位に流します。

`app/components/Home.js`:

```
import React, {Component} from 'react';
import CommentBox from './CommentBox';

const data = [
  {author: "Pete Hunt", text: "This is one comment"},
  {author: "Jordan Walke", text: "This is *another* comment"}
];

class Home extends Component {
  render() {
    return (
      <div>
        <CommentBox data={data} />
      </div>
    );
  }
}

export default Home
```

* データを *CommentBox* から下位に流します。

`app/components/CommentBox.js`:

```
import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';

class CommentBox extends Component {
  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
}

export default CommentBox;
```

* データを *CommentList* から下位に流します。

`app/components/CommentList.js`:

```
import React, {Component} from 'react';
import Comment from './Comment';

class CommentList extends Component {
  render() {
    const commentNodes = this.props.data.map((comment, index) => {
      return (
        <Comment author={comment.author} key={index}>
          {comment.text}
        </Comment>
      );
    });

    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
}

export default CommentList;
```

![tutorial_8-10](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_8-10.png)

**Redux** を利用しなくともディレクトリの構造やモジュール化のテクニックで
ファイルを小さく保つことができます。

#### Tutorial 11 - 14

`GET /api/comments` から値が取得するアクションが発生するので、ここで **Redux** が登場します。
他に **React** と **Redux** をつなぐ **React Redux** や 非同期を調整する **Redux Thunk** のライブラリを利用します。

```bash
$ npm install redux --save
$ npm install react-redux --save
$ npm install redux-thunk --save
```

* まず、アクションを宣言します。

`app/constants/ActionTypes.js`:

```
export const RECEIVE_COMMENTS = 'RECEIVE_COMMENTS';
```

* Comment の Search と Recieve のアクションを追加します。
* 非同期は **ECMAScript 6** から利用できる Promise で宣言しています。

`app/actions/CommentActions.js`:

```
import * as ActionTypes from '../constants/ActionTypes';
import $ from 'jquery';

export function recieveComments(comments) {
  return {
    type: ActionTypes.RECEIVE_COMMENTS,
    comments
  };
}

export function searchComments() {
  return dispatch => {
    let promise = new Promise((resolve, reject) => {
      $.ajax({
        url: '/api/comments',
        dataType: 'json',
        cache: false,
        success(data) {
          resolve(data);
        },
        error(xhr, status, err) {
          reject(err);
        }
      });
    });

    promise.then((data) => {
      dispatch(recieveComments(data));
    }).catch((err) => {
      console.error(err);
    });
  };
}
```

* 複数の Reducer は `index.js` で宣言します。

`app/reducers/index.js`:

```
export {default as Comment} from './Comment';
```

* Comment の Recieve アクションから状態を取得します。

`app/reducers/Comment.js`:

```
import * as ActionTypes from '../constants/ActionTypes';

const initialState = {comments: []};

export default function(state = initialState, action) {
  switch (action.type) {
  case ActionTypes.RECEIVE_COMMENTS:
    return {comments: action.comments};
  default:
    return state;
  }
}
```

* *Store* を生成するときに Middleware や *Reducer* を取り入れます。

`app/store/configureStore.js`:

```
import {createStore, applyMiddleware, combineReducers} from 'redux';
import thunkMiddleware from 'redux-thunk';
import * as reducers from '../reducers/index';

let createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
const rootReducer = combineReducers(reducers);

export default function configureStore(initialState) {
  return createStoreWithMiddleware(rootReducer, initialState);
}
```

* **React Redux** で Provider を宣言をして *Store* を流します。

`app/containers/App.js`:

```
import React from 'react';
import {Provider} from 'react-redux';
import configureStore from '../store/configureStore';
import Home from '../components/Home';

const store = configureStore();

export default React.createClass({
  render() {
    return (
      <div>
        <Provider store={store}>
          <Home />
        </Provider>
      </div>
    );
  }
});
```

* *Action* を `dispatch` に登録する。

`app/components/Home.js`:

```
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as CommentActions from '../actions/CommentActions';
import CommentBox from './CommentBox';

class Home extends Component {
  render() {
    const {dispatch, comments} = this.props;
    const actions = bindActionCreators(CommentActions, dispatch);
    return (
      <div>
        <CommentBox actions={actions} data={comments} />
      </div>
    );
  }
}

export default connect(state => state.Comment)(Home)
```

* Search Comments のアクションを実施します。

`app/components/CommentBox.js`:

```
import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';

class CommentBox extends Component {
  componentDidMount() {
    this.props.actions.searchComments();
    setInterval(this.props.actions.searchComments, 2000);
  }

  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
}

export default CommentBox;
```

![tutorial_11-14](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_11-14.png)

構造的に小さなモジュールを繋げることは強いシステムになるので、**Redux** の考えは好ましいです。

#### Tutorial 15 - 20

* コメントを追加するアクションを宣言します。

`app/constants/ActionTypes.js`:

```
export const ADD_COMMENT = 'ADD_COMMENT';
```

* Comment の Create と Add のアクションを追加します。

`app/actions/CommentActions.js`:

```
export function addComment(comment) {
  return {
    type: ActionTypes.ADD_COMMENT,
    comment
  };
}

export function createComment(comment) {
  return dispatch => {
    dispatch(addComment(comment));
    let promise = new Promise((resolve, reject) => {
      $.ajax({
        url: '/api/comments',
        dataType: 'json',
        type: 'POST',
        data: comment,
        success(data) {
          resolve(data);
        },
        error(xhr, status, err) {
          reject(err);
        }
      });
    });

    promise.then((data) => {
      dispatch(recieveComments(data));
    }).catch((err) => {
      console.error(err);
    });
  };
}
```

* *ADD_COMMENT* でコメントが追加されます。

`app/reducers/Comment.js`:

```
import * as ActionTypes from '../constants/ActionTypes';

const initialState = {comments: []};

export default function(state = initialState, action) {
  switch (action.type) {
  case ActionTypes.RECEIVE_COMMENTS:
    return {comments: action.comments};
  case ActionTypes.ADD_COMMENT:
    return {comments: state.comments.concat([action.comment])};
  default:
    return state;
  }
}
```

* `onCommentSubmit` に `createComment` を設定します。

`app/components/CommentBox.js`:

```
import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';

class CommentBox extends Component {
  componentDidMount() {
    this.props.actions.searchComments();
    setInterval(this.props.actions.searchComments, 2000);
  }

  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm onCommentSubmit={this.props.actions.createComment} />
      </div>
    );
  }
}

export default CommentBox;
```

* `ReactDOM.findDOMNode` で `refs` の DOM を操作します。

`app/components/CommentForm.js`:

```
import React, {Component} from 'react';
import ReactDOM from 'react-dom';

class CommentForm extends Component {
  handleSubmit(e) {
    e.preventDefault();
    const author = ReactDOM.findDOMNode(this.refs.author).value.trim();
    const text = ReactDOM.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) {
      return;
    }
    this.props.onCommentSubmit({author: author, text: text});
    ReactDOM.findDOMNode(this.refs.author).value = '';
    ReactDOM.findDOMNode(this.refs.text).value = '';
    return;
  }

  render() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
}

export default CommentForm;
```

![tutorial_15-20](https://dl.dropboxusercontent.com/u/14690051/images/notes/flux/react-tutorial/tutorial_15-20.png)

これで **Redux** の *Comments Box* が完成しました。
**Redux** も **Tutorial** が必要かなと思って、**React Tutorial** を題材にしましたが、**Redux** はもっと多機能なので学習を深めたい。

----

## Tips

### WebSocket

サーバーとの通信に **Ajax** でなく **WebSocket** を利用してみましょう。
**WebSocket** は [Socket.IO](http://socket.io/) のライブラリを利用します。

```
$ npm install socket.io --save
$ npm install socket.io-client --save
```

#### Server

* **Express** の 実行ファイルの最後に *socket.io* の設定を追加します。

`bin/www`:

```
var io = require('socket.io')(server);
require('../channels/channel.js')(io);
```

* サーバーに `search comments` と `recieve comments` と `create comment` のチャンネルを追加します。
* また、`setInterval` をクライアントからサーバーに移設します。

`channels/channel.js`:

```
var fs = require('fs');

var channel = function(io) {
  io.on('connection', function(socket) {
    socket.on('search comments', function(){
      fs.readFile('db/comments.json', function(err, data) {
        socket.emit('recieve comments', JSON.parse(data));
      });
    });

    socket.on('create comment', function(comment){
      fs.readFile('db/comments.json', function(err, data) {
        var comments = JSON.parse(data);
        comments.push(comment);
        fs.writeFile('db/comments.json', JSON.stringify(comments, null, 4), function(err) {
          socket.emit('recieve comments', comments);
        });
      });
    });

    setInterval(function(){
      fs.readFile('db/comments.json', function(err, data) {
        socket.emit('recieve comments', JSON.parse(data));
      });
    }, 2000);
  })
};

module.exports = channel;
```

#### Client

* `$.ajax`(**jQuery**) を `socket.emit` に置換えます。

`app/actions/CommentActions.js`:

```
import * as ActionTypes from '../constants/ActionTypes';
import io from 'socket.io-client';
export const socket = io('http://localhost:3000');

export function recieveComments(comments) {
  return {
    type: ActionTypes.RECEIVE_COMMENTS,
    comments
  };
}

export function addComment(comment) {
  return {
    type: ActionTypes.ADD_COMMENT,
    comment
  };
}

export function searchComments() {
  return dispatch => {
    socket.emit('search comments');
  };
}

export function createComment(comment) {
  return dispatch => {
    socket.emit('create comment', comment);
  };
}
```

* `componentWillMount` に `recieve comments` の *Subscription* を設置する。

`app/components/CommentBox.js`:

```
import React, {Component} from 'react';
import CommentList from './CommentList';
import CommentForm from './CommentForm';
import io from 'socket.io-client';
export const socket = io('http://localhost:3000');

class CommentBox extends Component {
  componentWillMount() {
    const {actions} = this.props;
    socket.on('recieve comments', function(comments) {
      actions.recieveComments(comments);
    });
  }

  componentDidMount() {
    this.props.actions.searchComments();
  }

  render() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm onCommentSubmit={this.props.actions.createComment} />
      </div>
    );
  }
}

export default CommentBox;
```

サーバーに `socket.broadcast.emit` を追加すると `setInterval` は不要です。
*Comment Box* は **Socket.IO** の *Rooms* 機能で少し手を入れるとチャットルームが作れますね。

----

### PropTypes

[PropTypes](https://facebook.github.io/react/docs/reusable-components.html)は *Component* の引数(`props`) を定義することができます。
可読性が向上するので利用しましょう。（型も豊富にあります。）

* PropTypes.array
* PropTypes.bool
* PropTypes.func
* PropTypes.number
* PropTypes.object
* PropTypes.string
* PropTypes.node
* PropTypes.element
* PropTypes.instanceOf(Message)
* PropTypes.oneOf(['News', 'Photos'])
* PropTypes.oneOfType([PropTypes.string, PropTypes.number)
* PropTypes.arrayOf(PropTypes.number)
* PropTypes.objectOf(PropTypes.number)
* PropTypes.shape({color: PropTypes.string, fontSize: PropTypes.number})
* PropTypes.any.isRequired

#### Example

* `{Component, PropTypes}` で宣言をして、`propTypes = {}` で定義します。

`app/components/Comment.js`:

```
import React, {Component, PropTypes} from 'react';

class Comment extends Component {
  rawMarkup() {
    let rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return { __html: rawMarkup };
  }

  render() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
}

Comment.propTypes = {
  author: PropTypes.string.isRequired,
  children: PropTypes.string.isRequired
};

export default Comment;
```

**Enjoy React**