React/Reduxのtips

#React/Reduxのいろいろなtips
React/Reduxを使っていて実装方法について苦戦して調査して解決したことをtipとしてまとめていく。

##スプレッド演算子（`...`）を利用するとエラー
スプレッド演算子を使うと`Module build failed: SyntaxError: Unexpected token`といったエラーが発生したので、調査をしたところ、`babel-preset-stage-2`が必要であることが判明。

そのため、`babel-preset-stage-2`をローカルインストールする。

```shell-session
$ yarn add --dev babel-preset-stage-2
```

なお、`npm`を利用する場合は以下。

```shell-session
$ npm install --save-dev babel-preset-stage-2
```

`webpack.config.js`に`stage-2`を指定する必要がある。

```js:webpack.config.js
  module: {
    loaders: [
      {
        test: /\.jsx$/,
        exclude: /(node_modules|.git)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react', 'stage-2']
        }
      }
    ]
  }
```

##classNameに複数class指定する
`className`に複数のclass名を指定する場合、以下のように記述。

```js
return (<div className={ `uk-navbar-nav uk-hidden-small`} />)
```

さらに、下記のような書き方ができるので、汎用性はある。

```js
const navi = "uk-navbar-nav"
const opt = "uk-hidden-small"
return (<div className={ `${navi} ${opt}`} />)
```

##styleを利用する
styleを利用する場合は、以下のように記述。

```html
return (<div style={{ textAlign: 'center', margin: '10px 0px' }} />)
```

##`jsx`のコメントアウト
複数行の場合は`{/* */}`で囲み、1行の場合は`{//`をコメントアウトしたい箇所の前に記載し、改行をした上で`}`を記載する。

```js
return (
  <div className="test">
    <div className="test-title">
      <i className="test-icon" />
    </div>
    {/*
    <div className="test-body">
      <p>test body</p>
    </div>
    */}
    <div className="test-comment">
      {// <p>test comment</p> 
      }
    </div>
  </div>

```

##複数のstoreを利用する
[こちら](http://qiita.com/hiroykam/items/408f3ab7c240ca2df02d)でComponentを使いまわすときに`combineReducers`で2つの`Action`を登録するようにしたが、下記のように`createStore`で2つ`store`を作成することでも実現できる。

```js:react/react_test/src/app.jsx
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer.jsx'
import DemoComponent from './DemoComponent.jsx'

const store1 = createStore(reducer, applyMiddleware(thunk))
const store2 = createStore(reducer, applyMiddleware(thunk))

render (
  <Provider store={ store1 }>
    <DemoComponent />
  </Provider>,
  document.querySelector('.react_container1')
)

render (
  <Provider store={ store2 }>
    <DemoComponent />
  </Provider>,
  document.querySelector('.react_container2')
)
```
なお、2つの`Provider`で同じ`store`を指定した場合、`store`は共有される。`reducer`をどのように用意するか、毎回悩まされる。


##`action`で`state`を取得
`action`から`state`で定義したいずれかの値を参照することがある。
その場合、以下の2つの方法がある。

###方法1
非同期処理などで`action`から別の`action`を実行する際、第1引数に`dispatch`を受け取る無名関数をリターンする処理をしたが、第2引数に`getState`を受け取るように追加することで`state`を参照することができる。

```js
import { state } from 'state'

const test1 = () => {
  return {
    type: state.TEST1
  }
}

const test2 = () => {
  return {
    type: state.TEST2
  }
}

const dispatchTest = () => {
  return (dispatch, getState) => {
    if (getState().myState.type === state.TEST1) {
      dispatch(test2())
    } else {
      dispatch(test1())
    }
  }
}
```

###方法2
`export const store = createStore(reducer, applyMiddleware(thunk))`のように`store`を`store.jsx`といったように用意し、そのjsxを`action`側で`import`することで下記の例のように`state`の参照が可能。

```js
import { state } from 'state'
import { store } from 'store'

const dispatchTest = () => {
  if (store.getState().myState.type === state.TEST1) {
    return {
      type: state.TEST2
    }
  } else {
    return {
      type: state.TEST1
    }
  }
}
```

##コンテナ（コンポーネント）の初期化時にActionを実行する
下記のように`Component`を継承したコンテナ（コンポーネント）で`componentDidMount`をオーバーライドし、`this.props`から`action`を抽出し、実行することで、実現できる。

```js
class TestContainer extends Component {
  componentDidMount() {
    const { actions } = this.props
    actions.testMethod()
  }

  render() {
    return (
      <div>test</div>
    )
  }
}
```

##ループ処理で動的な要素を作成
`json`で受信したデータからアイコンリストのような要素を動的にループ処理で作成する場合、下記のように実装できる。


```js
import React from 'react'

export const comp = ({ json }) => {
  const mu = Object.keys(json).map((key) => {
    return (
      <div key={ key } >
       <img src={ json[key].icon } /><br />
      </div>
    )
  })
  return (
    <div>
      { mu }
    </div>
  )
}
```

##ショートカットキーを導入し、`action`を実行
`redux-shortcuts`をローカルインストールする。

```shell-session
$ yarn add --dev redux-shortcuts
```

なお、`npm`を利用する場合は以下。

```shell-session
$ npm install --save-dev redux-shortcuts
```

下記のように`bindShortcuts`でショートカットキーに対して`action`を割り当てることができる。
なお、この例では、`bindActionCreators`で`dispatchTest`がコンポーネントにマッピングされていることが前提。

```js
import { bindShortcuts } from 'redux-shortcuts'
import { dispatchTest } from './actions'
import { reducer } from './reducer'

const store = createStore(reducer, applyMiddleware(thunk))

bindShortcuts(
  [['command+d', 'ctrl+d'], dispatchTest]
)(store.dispatch)
```

##`redux-devtools`を使った`action`結果（履歴）と`state`変更（履歴）の確認
`redux-devtools`を使うと、`action`実行結果（履歴）や`state`の更新（履歴）や内容を画面上に表示して確認することができる。
[こちら](https://qiita.com/hiroykam/items/408f3ab7c240ca2df02d)のサンプルを利用する。

###モジュールのインストール
必要なモジュールをインストールする。

```shell-session
$ yarn add --dev redux-devtools
$ yarn add --dev redux-devtools-log-monitor
$ yarn add --dev redux-devtools-dock-monitor
```

`npm`を利用する場合は以下。

```shell-session
$ npm install --save-dev redux-devtools
$ npm install --save-dev redux-devtools-log-monitor
$ npm install --save-dev redux-devtools-dock-monitor
```

###サンプルの修正
`app.jsx`を以下のように変更する。

```js:react/react_test/src/app.jsx
mport React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import reducer from './reducer.jsx'
import DemoComponent from './DemoComponent.jsx'

import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'

const DevTools = createDevTools(
  <DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q">
    <LogMonitor theme="tomorrow" preserveScrollTop={false} />
  </DockMonitor>
)

const store = createStore(reducer, DevTools.instrument(), applyMiddleware(thunk))

render (
  <Provider store={ store }>
    <div>
    <DemoComponent maxLengthA="3" maxLengthB="6" />
    <DevTools />
    </div>
  </Provider>,
  document.querySelector('.react_container')
)
```

###デモ
ビルドして実行すると、下記のように表示される。
`CSV`ファイルを読み込ませることで、`action`の実行結果や、`state`の更新内容が追加されていく。

<img width="701" alt="スクリーンショット 2017-11-04 21.37.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/135559/f99c19a2-0479-09bf-4b3d-93611918e45b.png">

##メンション機能
SNSで利用機会があるメンション機能。`React`では[`react-mentions`](https://github.com/effektif/react-mentions)が用意されている。結構カスタマイズができて便利のように思えたが、ドキュメント等がないので、コードを見て、検証する必要があった。
ここではメンションの対象となるユーザを非同期で取得する。

###必要なモジュールのインストール
```shell-session
$ yarn init
$ yarn add --dev webpack
$ yarn add --dev react react-dom redux react-redux redux-thunk
$ yarn add --dev babel-loader babel-core babel-preset-es2015 babel-preset-react babel-preset-stage-2
$ yarn add --dev style-loader css-loader
$ yarn add --dev react-mentions
```

###サンプルのディレクトリ構成
<img width="275" alt="スクリーンショット 2017-08-30 18.39.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/135559/ec935ca4-26b3-6039-050f-1f607c87df52.png">


`defaultMentionStyle.js`と`defaultStyle.js`については、[ここ](https://github.com/effektif/react-mentions/tree/master/gh-pages/views/examples)から取得する。

なお、メンションで候補として表示されるポップアップのスタイルを変更したい場合は以下のように`suggestions`直下に記載する（本サンプルでは`github`にあるものをそのまま利用した）。

```css:defaultStyle.js
export default ({
  suggestions: {
    display: "inline-block",
    position: "unset",
    float: "left",
    overflow: "scroll",
    maxHeight: "300px",
    marginLeft: "50%",

    list: {
      display: "inline-block",
      position: "unset",
      float: "left",
      backgroundColor: 'white',
      fontSize: 10,
    },

    item: {
      position: "relative",
      '&focused': {
        backgroundColor: '#cee4e5',
      },
    },
  }
})
```

####ファイルの内容

```json:package.json
{
  "name": "mention",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-mentions": "^1.2.0",
    "react-redux": "^5.0.6",
    "redux": "^3.7.2",
    "redux-thunk": "^2.2.0",
    "webpack": "^3.5.5"
  },
  "scripts": {
    "prod": "webpack -p",
    "dev": "webpack -d"
  }
}
```

```js:webpack.config.js
var webpack = require('webpack')

module.exports = {
  entry: {
    './js/app': './jsx/app.jsx'
  },
  output: {
    path: __dirname,
    filename: '[name].bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.jsx$/,
        exclude: /(node_modules|.git)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react', 'stage-2']
        }
      }
    ]
  }
}
```

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>React mention</title>
  </head>
  <body>
    <div class="react_mention"></div>
    <script type="text/javascript" src="js/app.bundle.js"></script>
  </body>
</html>
```

```js:jsx/actions.jsx
export const TYPE_NONE     = "TYPE_NONE"
export const TYPE_MENTION  = "TYPE_MENTION"
export const TYPE_UNLOADED = "TYPE_UNLOADED"
export const TYPE_LOADING  = "TYPE_LOADING"
export const TYPE_LOADED   = "TYPE_LOADED"

const data = [
  {
    id: 'walter',
    display: 'Walter White',
  },
  {
    id: 'jesse',
    display: 'Jesse Pinkman',
  },
  {
    id: 'gus',
    display: 'Gustavo "Gus" Fring',
  },
  {
    id: 'saul',
    display: 'Saul Goodman',
  },
  {
    id: 'hank',
    display: 'Hank Schrader',
  },
  {
    id: 'skyler',
    display: 'Skyler White',
  },
  {
    id: 'mike',
    display: 'Mike Ehrmantraut',
  }
]

const loading = () => {
  return {
    type: TYPE_LOADING
  }
}

const users = (callback) => {
  callback(data)
  return {
    type: TYPE_LOADED,
    users: data
  }
}

export const loadUsers = (callback) => {
  return (dispatch, getState) => {
    if (getState().demo.status == TYPE_UNLOADED) {
      dispatch(loading())
      setTimeout(() => {
        dispatch(users(callback))
      }, 1000)
    }
  }
}

export const handleMention = (e, v, tp, m) => {
  return {
    type: TYPE_MENTION,
    value: v,
    mentions: m,
  }
}
```

```js:jsx/reducer.jsx
import { combineReducers } from 'redux'
import * as actions from './actions.jsx'

export const initialState = {
  value: "",
  mentions: [],
  status: actions.TYPE_UNLOADED,
  users: [],
}

const demo = (state = initialState, action) => {
  if (action.type === actions.TYPE_LOADING) {
    return {
      ...state,
      status: actions.type
    }
  } else if (action.type === actions.TYPE_LOADED) {
    return {
      ...state,
      status: action.type,
      users: action.users,
    }
  } else if (action.type === actions.TYPE_MENTION) {
    return {
      ...state,
      value: action.value,
      mentions :action.mentions
    }
  } else {
    return state
  }
}

const reducer = combineReducers({
  demo
})

export default reducer
```

※非同期でのメンション対象となるユーザ情報が必要ない場合、`Mention`の`data`プロパティに直接配列を指定する。

```js:jsx/container.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as myActions from './actions.jsx'
import { MentionsInput, Mention } from 'react-mentions'
import defaultStyle from './defaultStyle.jsx'
import defaultMentionStyle from './defaultMentionStyle.jsx'

class DemoContainer extends Component {
  render() {
    const { demo, actions } = this.props

    let data = null
    if (demo.status === myActions.TYPE_UNLOADED) {
      data = (search, callback) => {
        actions.loadUsers(callback)
      }
    } else {
      data = demo.users
    }

    return (
      <div style={{ width: "300px", marginLeft: "200px" }} >
        <MentionsInput
          style={ defaultStyle }
          value={ demo.value }
          onChange={ actions.handleMention }
          placeholder={ "Mention people using '@'" }
          displayTransform={ (id, display) => `@${display}` } >

        <Mention
          trigger="@"
          data={ data }
          renderSuggestion={
            (suggestion, search, highlightedDisplay) => (
              <div className="user">
                { highlightedDisplay }
              </div>
            )
          }
          onAdd = {
            (id, display) => {
              console.log(display)
            }
          }
          style={ defaultMentionStyle }
        />
      </MentionsInput>
      </div>
    )
  }
}

const mapState = (state) => ({
  demo: state.demo
})

const mapDispatch = (dispatch) => ({
  actions: bindActionCreators(myActions, dispatch)
})

export default connect(mapState, mapDispatch)(DemoContainer)
```

```js:jsx/app.jsx
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer.jsx'
import DemoContainer from './container.jsx'

const store = createStore(reducer, applyMiddleware(thunk))

render (
  <Provider store={ store }>
    <DemoContainer />
  </Provider>,
  document.querySelector('.react_mention')
)
```

###ビルド
下記のコマンドでビルドする。

```shell-session
$ yarn run prod
```

###デモ
`index.html`を実行すると以下のように表示される。`@`をタイプすると1秒後に（初回のみ）メンション対象のユーザ一覧が表示され、さらにタイプすることでユーザが絞り込まれる。

<img width="457" alt="スクリーンショット 2017-08-30 18.38.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/135559/ec89cdec-3d33-6400-db64-929e69a1c853.png">


##無限スクロール
いわゆるレイジースクロールといったところ。`Redux`用に[redux-infinite-scroll](https://github.com/RealScout/redux-infinite-scroll)というライブラリがあるが、下方向スクロールには対応しているが、上方向のスクロールには対応していない。そこで拡張して利用する。
非同期を考慮して、あえて`setTimeout`で1秒間waitするようにした。

###必要なモジュールのインストール
```shell-session
$ yarn init
$ yarn add --dev webpack
$ yarn add --dev react react-dom redux react-redux redux-thunk
$ yarn add --dev babel-loader babel-core babel-preset-es2015 babel-preset-react babel-preset-stage-2
$ yarn add --dev style-loader css-loader
$ yarn add --dev redux-infinite-scroll
```

###サンプルのディレクトリ構成
<img width="264" alt="スクリーンショット 2017-08-27 19.04.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/135559/3861d23a-0d16-a9ee-1942-d8645fc4b1e7.png">

[ここ](https://github.com/RealScout/redux-infinite-scroll/tree/gh-pages/stylesheets)から`main.css`と`stylesheet.css`を取得しておく。

最低限`css`で、以下の定義を忘れると、スクロールしない・スクロールイベントが発生しないなどの不備が発生するので、注意。

```css
.redux-infinite-scroll {
    overflow: scroll;
}
```

####ファイルの内容

```json:package.json
{
  "name": "infinite_scroll",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1",
    "css-loader": "^0.28.5",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-redux": "^5.0.6",
    "redux": "^3.7.2",
    "redux-infinite-scroll": "^1.0.9",
    "redux-thunk": "^2.2.0",
    "style-loader": "^0.18.2",
    "webpack": "^3.5.5"
  },
  "scripts": {
    "prod": "webpack -p",
    "dev": "webpack -d"
  }
}
```

```js:webpack.config.js
var webpack = require('webpack')

module.exports = {
  entry: {
    './js/app': './jsx/app.jsx'
  },
  output: {
    path: __dirname,
    filename: '[name].bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        loaders: ['style-loader', 'css-loader?modules'],
      },
      {
        test: /\.jsx$/,
        exclude: /(node_modules|.git)/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'react', 'stage-2']
        }
      },
      {
        test: /\.js$/,
        exclude: /(node_modules|.git)/,
        loader: "babel-loader",
        query:{
          presets: ['es2015']
        }
      }
    ]
  }
}
```

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>React infnite scroll test</title>
    <link rel="stylesheet" type="text/css" href="css/main.css" media="screen">
    <link rel="stylesheet" type="text/css" href="css/stylesheet.css" media="screen">
  </head>
  <body>
    <div class="react_scroll"></div>
    <script type="text/javascript" src="js/app.bundle.js"></script>
  </body>
</html>
```

```js:jsx/actions.jsx
export const PREPARE1 = "PREPARE1"
export const PREPARE2 = "PREPARE2"
export const MESSAGE1 = "MESSAGE1"
export const MESSAGE2 = "MESSAGE2"

const preparemessages = (n) => {
  const state = (n == 1) ?  PREPARE1 : PREPARE2
  return {
    type: state
  }
}

const createMessages = (n, arr = []) => {
  let lst = arr || []
  let start = arr.length + 1
  for (let i = start; i < start + 20; i++) {
    lst.push(i)
  }
  const state = (n == 1) ?  MESSAGE1 : MESSAGE2
  return {
    type: state,
    msg: lst
  }
}

export const loadMore = (n) => {
  return (dispatch, getState) => {
    dispatch(preparemessages(n))
    let messages = (n == 1) ? getState().demo.messages1 : getState().demo.messages2
    setTimeout(() => {
      dispatch(createMessages(n, messages))
    }, 1000)
  }
}
```


```js:jsx/reducer.jsx
import { combineReducers } from 'redux'
import * as actions from './actions.jsx'

export const initialAppState = {
  messages1: [],
  loadingMore1: false,
  messages2: [],
  loadingMore2: false
}

const demo = (state = initialAppState, action) => {
  if (action.type === actions.PREPARE1) {
    return {
      ...state,
      loadingMore1: true,
    }
  } else if (action.type === actions.MESSAGE1) {
    return {
      ...state,
      messages1: action.msg,
      loadingMore1: false
    }
  } else if (action.type === actions.PREPARE2) {
    return {
      ...state,
      loadingMore2: true,
    }
  } else if (action.type === actions.MESSAGE2) {
    return {
      ...state,
      messages2: action.msg,
      loadingMore2: false
    }
  } else {
    return state
  }
}

const reducer = combineReducers({
  demo
})

export default reducer
```

```js:jsx/ReduxInfiniteScrollEx.jsx
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
import ReduxInfiniteScroll from 'redux-infinite-scroll'

export default class ReduxInfiniteScrollEx extends ReduxInfiniteScroll {

  constructor(props) {
    super(props)
    this._pastLength = 0
  }

  componentDidUpdate () {
    const currentLength = this._scrollHeight()
    if (currentLength > this._pastLength) {
      ReactDOM.findDOMNode(this).scrollTop += (currentLength - this._pastLength)
    }
    if (currentLength !== this._pastLength) {
      this._pastLength = currentLength
    }

    this.attachScrollListener();
  }

  _scrollHeight() {
    return ReactDOM.findDOMNode(this).scrollHeight
  }

  _elScrollListener() {
    let el = ReactDOM.findDOMNode(this)

    if (this.props.horizontal) {
      let leftScrollPos = el.scrollLeft
      let totalContainerWidth = el.scrollWidth
      let containerFixedWidth = el.offsetWidth
      let rightScrollPos = leftScrollPos + containerFixedWidth

      return (totalContainerWidth - rightScrollPos)
    }

    return el.scrollTop
  }

  scrollListener() {
    if (this._totalItemsSize() <= 0) return

    let bottomPosition = this.props.elementIsScrollable ? this._elScrollListener() : this._windowScrollListener()

    if (bottomPosition === 0) {
      this.detachScrollListener()
      this.props.loadMore()
    }
  }

  render () {
    const Holder = this.props.holderType

    return (
      <Holder className={ this._assignHolderClass() } style={{height: this.props.containerHeight, overflow: 'scroll'}}>
        {this.renderLoader()}
        {this.props.animateItems ? this._renderWithTransitions() : this._renderOptions()}
      </Holder>
    )
  }
}
```

```js:jsx/container.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as actions from './actions.jsx'
import ReduxInfiniteScroll from 'redux-infinite-scroll'
import ReduxInfiniteScrollEx from './ReduxInfiniteScrollEx.jsx'

class DemoContainer extends Component {
  render() {
    const { demo, actions } = this.props

    const _renderMessages = (messages) => {
      return messages.map((msg) => {
        return(
          <div className="item" key={msg}>{msg}</div>
        )
      })
    }

    return (
      <div style={{ width: "300px", marginLeft: "200px" }} >
        <ReduxInfiniteScroll loadingMore={ demo.loadingMore1 } containerHeight="300px" loadMore={ () => actions.loadMore(1) } >
          { _renderMessages(demo.messages1) }
        </ReduxInfiniteScroll>
        <br />
        <ReduxInfiniteScrollEx loadingMore={ demo.loadingMore2 } containerHeight="300px" loadMore={ () => actions.loadMore(2) } >
          { _renderMessages(demo.messages2) }
        </ReduxInfiniteScrollEx>
      </div>
    )
  }
}

const mapState = (state) => ({
  demo: state.demo
})

const mapDispatch = (dispatch) => ({
  actions: bindActionCreators(actions, dispatch)
})

export default connect(mapState, mapDispatch)(DemoContainer)
```

```js:jsx/app.jsx
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer.jsx'
import DemoContainer from './container.jsx'

const store = createStore(reducer, applyMiddleware(thunk))

render (
  <Provider store={ store }>
    <DemoContainer />
  </Provider>,
  document.querySelector('.react_scroll')
)
```

###ビルド
下記のコマンドでビルドする。

```shell-session
$ yarn run prod
```

###デモ
`index.html`を実行すると以下のように表示される。上のリストは下方向にスクロールすると下にアイテムが追加されて、下のリストは上方向にスクロールすると上にアイテムが追加されていく（上手にスクリーンショットがとれなかった）。

<img width="375" alt="スクリーンショット 2017-08-27 19.02.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/135559/0e4103a4-ff28-1670-0451-b8f8153ea580.png">


##RailsのActionCableとの連携
`npm`か`yarn`で`actioncable`関連のプラグインを導入することも検討したが、そもそも`cable.js`があり、グローバルで実行されているのであればプラグインが不要。

```js:cable.js
(function() {
  this.App || (this.App = {});
  App.cable = ActionCable.createConsumer();
}).call(this);
```

上記のように定義されていれば、`react`側で`App.cable`を利用すれば、いいので、下記のように`Action`を定義できる。ただし、非同期で処理するようなケースでは、`dispatch`が必要なので、その点は注意する。
また、`bindActionCreators`で`ChatChannel`をコンポーネントにマッピングする必要がある。

```js
export const ChatChannel = App.cable.subscriptions.create({channel: "ChatChannel" },
  {
    connected: () => {
      console.log("connected")
    },
    disconnected: () => {
      console.log("disconnected")
    },
    received: (data) => {
      console.log("received",  data)
    },
    speak: () => {
      return (dispatch, getState) => {
        const msg = getState().demo.msg
        BrandChannel.perform('speak', { msg })
        console.log("speak")
      }
    }
  }
)
```