LoginSignup
2
5

More than 5 years have passed since last update.

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

Posted at

概要

  • 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

コード

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)

参考

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5