てくてくあるく

WordPress の テーマ とか プラグイン に ついて 勉強しています

今回は サイドメニュー に WordPress の Rest API から取得した カテゴリー と タグ を 表示させます

前回の記事

React を 触ってみた (4) – Redux
https://tekuaru.jack-russell.jp/2018/05/01/1750/

WordPress の Rest API から データを取得 するには 非同期処理が 必要になります
その際 必要になるのが redux-thunk という パッケージ

また カテゴリ や タグ を クリックした際 URL も 変えたいと思うので
その際 必要になるのが react-router-dom という パッケージになります


redux-thunk と react-router-dom の インストール


(Dir : /var/www/html/)

今回も 作成に必要な パッケージを インストールします

npm install --save-dev redux-thunk react-router-dom

各々のバージョンは 以下のようになりました

"react-router-dom": "^4.2.2",
"redux-thunk": "^2.2.0",

ファイル作成


わかりやすいように src 以下に component-parts ディレクトリを 作成します

/var/www/html/
 -> src
  -> component-parts

その中に

list-categories.jsx
list-tags.jsx

の ファイルを 作成します

ファイル名から 分かる通り 各々 カテゴリー と タグ を リスト表示させるものです

早速ソースを見てもらいましょう


/src/component-parts/list-categories.jsx


import React from 'react'

import { Link } from 'react-router-dom'

import PropTypes from 'prop-types'

import { withStyles } from 'material-ui/styles'

import List, { ListSubheader, ListItem, ListItemText } from 'material-ui/List'

import { connect } from 'react-redux'

import { mapStateToProps, mapDispatchToProps } from '../redux/containers'

const styles = theme => ({
  drawerListSubheader: {
    backgroundColor: theme.palette.background.paper,
  },
})

class ListCategories extends React.Component {
  constructor (props) {
    super(props)
  }

  componentWillMount() {
//   componentDidMount() {
    var categoriesURL = this.props.API.default + this.props.API.namespace + 'categories/' + '?' + 'per_page=100'
    this.props.handleLoadCategories(categoriesURL)
  }

  render(){
    const { classes } = this.props

    return(
      <div>
        <List>
          <ListSubheader className={classes.drawerListSubheader} component="div">Categories</ListSubheader>
          {this.props.categories.map((item) => {
            return (
              <ListItem button key={item.id} component={Link} to={`/categories/${item.id}`}>
                <ListItemText primary={item.name} />
              </ListItem>
            )
          })}
        </List>
      </div>
    )
  }
}

ListCategories.propTypes = {
  classes: PropTypes.object.isRequired,
  theme: PropTypes.object.isRequired,
}

export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles, { withTheme: true })(ListCategories))

/src/component-parts/list-tags.jsx


import React from 'react'

import { Link } from 'react-router-dom'

import PropTypes from 'prop-types'

import { withStyles } from 'material-ui/styles'

import List, { ListSubheader, ListItem, ListItemText } from 'material-ui/List'

import { connect } from 'react-redux'

import { mapStateToProps, mapDispatchToProps } from '../redux/containers'

const styles = theme => ({
  drawerListSubheader: {
    backgroundColor: theme.palette.background.paper,
  },
})

class ListTags extends React.Component {
  constructor (props) {
    super(props)
  }

  componentWillMount() {
//   componentDidMount() {
    var tagsURL = this.props.API.default + this.props.API.namespace + 'tags/' + '?' + 'per_page=100'
    this.props.handleLoadTags(tagsURL)
  }

  render(){
    const { classes } = this.props

    return(
      <div>
        <List>
          <ListSubheader className={classes.drawerListSubheader} component="div">Tags</ListSubheader>
          {this.props.tags.map((item) => {
            return (
              <ListItem button key={item.id} component={Link} to={`/tags/${item.id}`}>
                <ListItemText primary={item.name} />
              </ListItem>
            )
          })}
        </List>
      </div>
    )
  }
}

ListTags.propTypes = {
  classes: PropTypes.object.isRequired,
  theme: PropTypes.object.isRequired,
}

export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles, { withTheme: true })(ListTags))

もともと main.jsx に あった

<List>
  <ListSubheader className={classes.drawerListSubheader} component="div">First List</ListSubheader>
  <ListItem button>
    <ListItemText primary="First List Button" />
  </ListItem>
</List>
<List>
  <ListSubheader className={classes.drawerListSubheader} component="div">Second List</ListSubheader>
  <ListItem button>
    <ListItemText primary="Second List Button" />
  </ListItem>
</List>

の 2つを 切り出した形に なっています


componentWillMount & componentDidMount とは


  componentWillMount() {
    /* 処理 */
  }
  componentDidMount() {
    /* 処理 */
  }

今回 増えた componentWillMount や componentDidMount は マウント(呼び出し)されるときに 1度だけ 実行されます

componentWillMount は render 前 に 処理 されて
componentDidMount は render 後 に 処理 されます

今回は カテゴリー や タグ を 取得するための 関数を 呼び出しています


component={Link} to={`/hoge/${huga.id}`} とは


こちらが クリックした際 URL も 変えるための 処理になります

Link という コンポーネントを 読み込み どのようなURL に 飛ぶのかは to で 指定しています

今回は htaccess による リダイレクト処理をしていないので 全部 ハッシュ で 渡していて 見た目だけの URLを 変えています


Redux の 処理追記


今回 handleLoadCategories と handleLoadTags という 新しい 関数を 追加したので その処理を 書いていきます


/src/redux/containers.jsx


import React from 'react'

import { connect } from 'react-redux'

import Main from '../component/main'

import { toggleDrawer, loadCategories, loadTags } from './actions'

export function mapStateToProps(state) {
  return state
}

export function mapDispatchToProps(dispatch) {
  return {
    handleToggleDrawer: () => { dispatch( toggleDrawer() ) },

    handleLoadCategories: (value) => { dispatch( loadCategories(value) ) },
    handleLoadTags: (value) => { dispatch( loadTags(value) ) },
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Main)

list-categories.jsx と list-tags.jsx から mapStateToProps と mapDispatchToProps を 呼び出すようになったため export を 追加

mapDispatchToProps に
handleLoadCategories: (value) => { dispatch( loadCategories(value) ) }, と
handleLoadTags: (value) => { dispatch( loadTags(value) ) }, を 追加

import に loadCategories と loadTags を 追加

が 今回の 変更点です


/src/redux/actions.jsx : 追記


export function loadCategories( value ) {
  return function(dispatch) {
    fetch(value)
      .then(response => response.json())
      .then(result => dispatch(setCategories(result)))
      .catch(error => console.log(error))
    // end fetch
  }
}

export function setCategories( value ) {
  return {
    type: 'SET-CATEGORIES',
    value,
  }
}

export function loadTags( value ) {
  return function(dispatch) {
    fetch(value)
      .then(response => response.json())
      .then(result => dispatch(setTags(result)))
      .catch(error => console.log(error))
    // end fetch
  }
}

export function setTags( value ) {
  return {
    type: 'SET-TAGS',
    value,
  }

loadCategories や loadTags は fetch 処理を通して setCategories や setTags に 渡すようになっています

fetch の 結果が帰ってきたら アクションを起こすように 2つに分けて 記述しているのが ポイントになります


/src/redux/reducers.jsx : function のみ


export default function reducer(state = initialState, action) {
  switch(action.type) {

    case 'TOGGLE-DRAWER':
      console.log( 'mobileOpen : ' + !state.mobileOpen )
      return Object.assign({}, state, {
        mobileOpen: !state.mobileOpen,
      })

    case 'SET-CATEGORIES':
      console.log( 'categories :' )
      console.log( action.value )
      return Object.assign({}, state, {
        categories: action.value,
      })

    case 'SET-TAGS':
      console.log( 'tags :' )
      console.log( action.value )
      return Object.assign({}, state, {
        tags: action.value,
      })

    default:
      return state
  }
}

SET-CATEGORIES や SET-TAGS 記述方法は TOGGLE-DRAWER と 大体おなじになります


ListCategories と ListTags に 差し替え


list-categories.jsx と list-tags.jsx を 作ったので main.jsx から 呼び出して 表示するようにします


/src/component/main.jsx : import部


import React from 'react'

import PropTypes from 'prop-types'

import { withStyles } from 'material-ui/styles'

import AppBar from 'material-ui/AppBar'
import Toolbar from 'material-ui/Toolbar'
import IconButton from 'material-ui/IconButton'
import MenuIcon from '@material-ui/icons/Menu'
import Hidden from 'material-ui/Hidden'
import Drawer from 'material-ui/Drawer'
import Divider from 'material-ui/Divider'

import Typography from 'material-ui/Typography'

import ListCategories from '../component-parts/list-categories'
import ListTags from '../component-parts/list-tags'

import List, { ListSubheader, ListItem, ListItemText } from ‘material-ui/List’ は
list-categories.jsx と list-tags.jsx 側に 持つので 必要なくなりました

その代わり
import ListCategories from ‘../component-parts/list-categories’
import ListTags from ‘../component-parts/list-tags’
を インポートしています


/src/component/main.jsx : const drawer 部


    const drawer = (
      <div>
        <ListCategories />
        <Divider />
        <ListTags />
      </div>
    )

ListCategories と ListTags に 単純に差し替えた形です


redux-thunk と react-router-dom を 連携


このままでは 非同期処理が 正しく処理されませんし

list-categories.jsx と list-tags.jsx に Link to を 設定しましたが 動かないので 連携を行っていきます


/src/index.jsx


import React from 'react'
import ReactDOM from 'react-dom'

import { HashRouter, Route } from 'react-router-dom'

import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'

import thunk from 'redux-thunk'

import Containers from './redux/containers'
import Reducers from './redux/reducers'

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

ReactDOM.render(
  <Provider store={store}>
    <HashRouter basename='/'>
      <div>
        <Route component={Containers} />
      </div>
    </HashRouter >
  </Provider>,
  document.getElementById('root')
)

ココの記述は 連携を行うたびに ガラッと変わっている気がします…

今回は
import { HashRouter, Route } from ‘react-router-dom’
import { applyMiddleware } from ‘redux’
import thunk from ‘redux-thunk’
が 新規で import されています

また store に applyMiddleware(thunk) が 追記され 非同期処理が 行えるようになっています

<Containers />

だけだったのが

<HashRouter basename='/react'>
  <div>
    <Route exact path='/' component={Containers} />
  </div>
</HashRouter >

と なっており HashRouter や Route を 通るようになりました


ページを確認


First React Project
http://react/

サイドメニューが 読み込まれているか 確認してみましょう
( コンパイルを 忘れずにね )


まとめ


今回は 単純な ソースの紹介になってしまいました…

我ながら 読みにくい 稚拙な文章だなって思います…

これからも ソースの紹介になってしまうかと思いますが ご購読お願い致します

現状 カテゴリ や タグ は 100件までしか 取得していないので
次回は それ以上件数も 取得するように 調整 や ソースコードの 整理を 行おうと考えています

Related Article

Gutenberg の カスタムブロック を 作ってみる (create-guten-block)

詳細へ »

React で 複数のスタイル を 指定する方法

詳細へ »