てくてくあるく

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/)

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

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

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

1
2
"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


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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 に あった

1
2
3
4
5
6
<List>
  <ListSubheader className={classes.drawerListSubheader} component="div">First List</ListSubheader>
  <ListItem button>
    <ListItemText primary="First List Button" />
  </ListItem>
</List>
1
2
3
4
5
6
<List>
  <ListSubheader className={classes.drawerListSubheader} component="div">Second List</ListSubheader>
  <ListItem button>
    <ListItemText primary="Second List Button" />
  </ListItem>
</List>

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


componentWillMount & componentDidMount とは


1
2
3
4
5
6
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


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
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 : 追記


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 のみ


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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部


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
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 部


1
2
3
4
5
6
7
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


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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) が 追記され 非同期処理が 行えるようになっています

1
<Containers />

だけだったのが

1
2
3
4
5
<HashRouter basename='/react'>
  <div>
    <Route exact path='/' component={Containers} />
  </div>
</HashRouter >

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


ページを確認


First React Project
http://react/

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


まとめ


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

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

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

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

Related Article

React を 触ってみた (7) – 再帰処理 で全ての Taxonomy を 取得する

詳細へ »

React を 触ってみた (8) – 記事一覧 を 表示

詳細へ »

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

詳細へ »