概要
- 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" } }
アプリ例
/
にアクセスすると下記が表示されます。
コード
rails
react-router用のrouting
今回実装するreact アプリのエントリーポイントは/
なのでこれだけでOK。
さらに、/about
や/items/:id
もreact-routerで管理する場合は、例えば、get '/about', to: 'home#index'
とget '/items/:id', to: 'home#index'
のようにエントリーポイントのアクションを指定するのがポイント。
Rails.application.routes.draw do
root 'home#index'
end
空のアクションを定義
class HomeController < ApplicationController
def index
end
end
javascriptを読み込み、アプリを乗っけるとdivタグとidを準備
<%= 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
モーダル開閉のアクションを実装します。
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に登録します。
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をまとめます。
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をミドルウェアに登録する。
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
で囲みます。
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もカスタムできるようにしました。
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を適用しています。
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。
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です。
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)