（メモ）material-uiにreduxとreact-routerを適用するときのテンプレート


# 概要
* material-uiでアプリを作ってみたけど、stateの管理がつらくなってきたのでreduxを導入した
* 完全にreactでページが作りたいので、react-routerも導入した
* 色々なページのつぎはぎです。オリジナリティはすごい低め。

# 環境
* rails: 5.1.4
* webpacker 3.0.2
* npm
	
	```package.json
		{
	  "name": "qrnote",
	  "private": true,
	  "dependencies": {
	    "@rails/webpacker": "^3.0.2",
	    "axios": "^0.17.0",
	    "babel-preset-react": "^6.24.1",
	    "coffeescript": "1.12.7",
	    "date-fns": "^1.29.0",
	    "material-ui": "^1.0.0-beta.19",
	    "material-ui-icons": "^1.0.0-beta.17",
	    "prop-types": "^15.6.0",
	    "react": "^16.0.0",
	    "react-dom": "^16.0.0",
	    "react-redux": "^5.0.6",
	    "react-router-dom": "^4.2.2",
	    "redux": "^3.7.2",
	    "redux-thunk": "^2.2.0"
	  }
	}
	```

# アプリ例
`/`にアクセスすると下記が表示されます。

[![https://gyazo.com/02637646dfb0aaa6bf36b23480896308](https://i.gyazo.com/02637646dfb0aaa6bf36b23480896308.gif)](https://gyazo.com/02637646dfb0aaa6bf36b23480896308)


# コード
## rails
### react-router用のrouting
今回実装するreact アプリのエントリーポイントは`/`なのでこれだけでOK。

さらに、`/about`や`/items/:id`もreact-routerで管理する場合は、例えば、`get '/about', to: 'home#index'`と`get '/items/:id', to: 'home#index'`のようにエントリーポイントのアクションを指定するのがポイント。

```config/routes.rb
Rails.application.routes.draw do
  root 'home#index'
end
```

空のアクションを定義

```app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index 
  end
end
```

javascriptを読み込み、アプリを乗っけるとdivタグとidを準備

```app/views/home/index.html.erb
<%= javascript_pack_tag 'app_root' %>
<div id="app_root"></div>
```

## javascript
### ファイル一覧
```
app/javascript/actions/items.js
app/javascript/assets/theme.js
app/javascript/components/ItemCreationDialog.jsx
app/javascript/containers/App.jsx
app/javascript/containers/Home.jsx
app/javascript/images/QRnote.svg
app/javascript/packs/app_root.jsx
app/javascript/reducers/index.js
app/javascript/store/configureStore.js
```

### 抜粋

#### actions

モーダル開閉のアクションを実装します。


```app/javascript/actions/items.js
export const SHOW_ITEM_CREATION_MODAL = 'SHOW_ITEM_CREATION_MODAL';
export const CLOSE_ITEM_CREATION_MODAL = 'CLOSE_ITEM_CREATION_MODAL';

export const showItemCreationModal = () => {
  return {
    type: SHOW_ITEM_CREATION_MODAL,
  }
};

export const closeItemCreationModal = () => {
  return {
    type: CLOSE_ITEM_CREATION_MODAL
  }
};

```

#### reducers
上記で実装した、actionをreducerに登録します。

```app/javascript/reducers/items.js
import {
  SHOW_ITEM_CREATION_MODAL,
  CLOSE_ITEM_CREATION_MODAL
} from '../actions/items';

export default (state = {itemCreationModalOpen: false}, action) => {
  switch (action.type) {
    case SHOW_ITEM_CREATION_MODAL: {
      return {
        ...state,
        itemCreationModalOpen: true
      }
    }
    case CLOSE_ITEM_CREATION_MODAL: {
      return {
        ...state,
        itemCreationModalOpen: false
      }
    }
    default: {
      return state;
    }
  }
}
```

登録したreducerをまとめます。

```app/javascript/reducers/index.js
import {combineReducers} from 'redux';
import items from './items'

export default combineReducers({
  items,
});
```

#### reduxでpropsを受け取る準備
上記でまとめたreducersをimportします。

`import rootReducer from '../reducers';`のようにフォルダを指定すると、フォルダ配下のindex.jsを読み込みます。（これが最初わからなくて困った）

また、今回は使っていませんが、get リクエストで非同期リクエストをするために、redux-thunkをミドルウェアに登録する。

```app/javascript/store/configureStore.js
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';

const configureStore = initialState => {
  return createStore(
    rootReducer,
    initialState,
    applyMiddleware(thunk)
  );
};

export default configureStore
```


上記で準備したstoreを読み込むように`Provider`で囲みます。

```app/javascript/packs/app_root.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from '../containers/App'
import {Provider} from 'react-redux';
import configureStore from '../store/configureStore';

const store = configureStore();

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Provider store={store}>
      <App/>
    </Provider>,
    document.getElementById('app_root'),
  )
});
```

#### react-routerを使う準備
react-routerを使う場合は、`BrowserRouter`で囲んで、`Route`でcontainer（ここでは`Home`）を指定する。そうすると`/`にアクセスすると`Home` containerが表示される。

さらに`MuiThemeProvider`で囲で、materila-uiのthemeもカスタムできるようにしました。

```app/javascript/containers/App.jsx
import React from 'react'
import {MuiThemeProvider} from 'material-ui/styles';
import theme from '../assets/theme'
import {BrowserRouter, Switch, Route} from 'react-router-dom';
import Home from './Home'

class App extends React.Component {
  render() {
    return (
      <MuiThemeProvider theme={theme}>
        <BrowserRouter>
          <div>
            <Header/>
            <Switch>
              <Route exact path={'/'} component={Home}/>
            </Switch>
          </div>
        </BrowserRouter>
      </MuiThemeProvider>
    )
  }
}

export default App
```

material-uiのテーマファイル。blueを適用しています。

```app/javascript/assets/theme.js
import {createMuiTheme} from 'material-ui/styles';
import blue from 'material-ui/colors/blue';
// material-uiのblueをベースにprimary colorをブランドの色にする
const theme = createMuiTheme({
  palette: {
    primary: {
      ...blue,
    },
  },
});

export default theme
```

#### （例）reducerを使う
`ItemCreationDialog.jsx` はモーダルを描画するcomponentです。これを`Button`で開閉する事例です。

ポイントは、下記。`Home` containerにconnectでpropsとして渡したいstateとactionを指定します。

stateは、とりあえずpropsに渡しましたが、実際は選んだ方が指定が楽なので実用的です。使う時はthis.props.state名です。

actionはimportして書くだけ。使う時はthis.props.アクション名です。

```
export default connect(
  state => {
    return (state)
  },
  {
    showItemCreationModal,
  }
)(Home)
```

本題ではありませんが、画像の表示は`import QRnoteImage from '../images/QRnote.svg'`でimportして、`<img src={QRenoteImage}/>`のようにsrcに渡してあげればOK。


```app/javascript/containers/Home.jsx
import React from 'react'
import {connect} from 'react-redux'
import {
  showItemCreationModal,
} from '../actions/items'
import Grid from 'material-ui/Grid';
import Typography from 'material-ui/Typography'
import QRnoteImage from '../images/QRnote.svg'
import ItemCreationDialog from '../components/ItemCreationDialog'
import Button from 'material-ui/Button'

const style = {
  padding: '48px 24px',
  boxSizing: 'border-box',
  backgroundColor: '#2196f3',
  overflow: 'hidden'
};

class Home extends React.Component {
  render() {
    return (
      <div style={style}>
        <Grid container justify='center' alignItems='center' direction='column'>
          <Grid item>
            <img src={QRenoteImage}/>
          </Grid>
          <Grid item>
            <Typography type='display2'
                        color='default'
                        align='center'
                        gutterBottom
            >
              {'QRnote'}
            </Typography>
            <Typography type='body1'
                        color='default'
                        align='center'
                        gutterBottom
            >
              {'simple QR note'}
            </Typography>
          </Grid>
          <Grid item>
            <Button raised color='default'
                    onClick={e => this.props.showItemCreationModal()}
            >
              <Typography type='title' color='inherit'>
                {'create free reservation note with QR code !'}
              </Typography>
            </Button>
            <ItemCreationDialog/>
          </Grid>
        </Grid>
      </div>
    )
  }
}

export default connect(
  state => {
    return (state)
  },
  {
    showItemCreationModal,
  }
)(Home)

```

ダイアログのcontainerです。

```app/javascript/components/ItemCreationDialog.jsx
import React from 'react'
import {connect} from 'react-redux'
import {
  closeItemCreationModal
} from '../actions/items'

import Dialog, {
  DialogContent,
  DialogContentText,
  DialogTitle,
} from 'material-ui/Dialog';

class ItemCreationDialog extends React.Component {
  render() {
    return (
      <div>
        <Dialog open={this.props.items.itemCreationModalOpen} onRequestClose={e => this.props.closeItemCreationModal()}>
          <DialogTitle>Subscribe</DialogTitle>
          <DialogContent>
            <DialogContentText>
              To subscribe to new reservation note, please enter item name here. We will create instantly.
            </DialogContentText>
          </DialogContent>
        </Dialog>
      </div>
    )
  }
}

export default connect(
  state => {
    return (state)
  },
  {
    closeItemCreationModal
  }
)(ItemCreationDialog)
```

# 参考
* https://qiita.com/mpyw/items/a816c6380219b1d5a3bf
* https://qiita.com/hoture/items/b4ca1773580317e7112e
* https://qiita.com/mpyw/items/a816c6380219b1d5a3bf
* http://tango-ruby.hatenablog.com/entry/2017/07/06/004421
* https://www.youtube.com/watch?v=d2Mp66FFBgU