React を 触ってみた (5) – 非同期処理 と Router
今回は サイドメニュー に 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件までしか 取得していないので
次回は それ以上件数も 取得するように 調整 や ソースコードの 整理を 行おうと考えています