10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

react-router v4 と Redux

Last updated at Posted at 2019-06-23

react-router で Redux を利用すれば、history methods (push, replace, go, goBack, goForward) を自由に使えるようになり便利です。そのためにconnected-react-routerをインストールして使います。その使い方を以下に説明します。

connected-react-routerの概要

Webアプリ用にreact-router を使うには react-router-dom をインストールします。 ==> REACT TRAINING / REACT ROUTER

react-router のみではURLの変更を<Link>や<Redirect>でしか行えない。

connected-react-routerを使えば、ルーティング情報をRedux storeで管理し、pushやreplace関数でURL変更を行うことができます。つまりredux-thunk や redux-saga において、history methods (push, replace, go, goBack, goForward)を含むaction を dispatch 売ることができます。 ==> Connected React Router - github

※かつてよく使われていたreact-router-reduxは現在はメンテされておらず、connected-react-routerに置き換えられています。

Example

以下の過去記事で紹介したものをExampleとします。

http://yahoo-shopping.s3-website-ap-northeast-1.amazonaws.com/
Material-UI レスポンシブデザイン- AppBar & Drawer

react-router-domのBrowserRouterを、connected-react-routerのConnectedRouterで置き換えます。

historyはJavaScriptライブラリで、react-routerがバックグランドで利用しているものです。connected-react-routerはhistory methodを使いやすくしてくれます。 ==> ReactTraining/history

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'connected-react-router';
import createBrowserHistory from 'history/createBrowserHistory';
import App from './App';
import createStore from './createStore';

// connected-react-router - action経由でルーティングが可能、push,replace..
// historyを強化
const history = createBrowserHistory();
const store = createStore(history);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);

createStoreをconnected-react-routerを使うようにカスタマイズします。redux-thunk ミドルウェアも組み込みます。

src/createStore
import { createStore as reduxCreateStore, combineReducers, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import { routerMiddleware, connectRouter } from 'connected-react-router'

import * as reducers from './reducers';

// connected-react-router - action経由でルーティングが可能、push,replace..
// createStoreの再定義 - historyを引数で受け、connected-react-routerの利用を抽象化
export default function createStore(history) { 
  return reduxCreateStore( // オリジナル createStore の別名
    combineReducers({
      ...reducers,
      router: connectRouter(history),
    }),
    applyMiddleware(
      logger,
      thunk,
      routerMiddleware(history)
    )
  );
}

App.jsでRouteの定義を行っています。AppBarなどのMaterial-UIのコードは無視してください。

src/App.js
import React from 'react';
import PropTypes from 'prop-types';
import AppBar from '@material-ui/core/AppBar';
import CssBaseline from '@material-ui/core/CssBaseline';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import { withStyles } from '@material-ui/core/styles';

import { Switch, Route, Redirect } from 'react-router-dom';
import Ranking from './containers/Ranking';
import Nav from './containers/Nav';
import { connect } from 'react-redux'
import { styles } from './Styles';


class ResponsiveDrawer extends React.Component {

  handleDrawerToggle = () => {
    this.props.dispatch({ type: 'MOBILE_TOGGLE' })
  };

  render() {
    const { classes, theme } = this.props;
    return (
      <div className={classes.root}>
        <CssBaseline />
        <AppBar position="fixed" className={classes.appBar}>
          <Toolbar>
            <IconButton
              color="inherit"
              aria-label="Open drawer"
              onClick={this.handleDrawerToggle}
              className={classes.menuButton}
            >
              <MenuIcon />
            </IconButton>
            <Typography variant="h6" color="inherit" noWrap>
              Yhoo!ショッピング
            </Typography>
          </Toolbar>
        </AppBar>
        <nav className={classes.drawer}>
          {/* The implementation can be swapped with js to avoid SEO duplication of links. */}
          <Nav />
        </nav>
        <main className={classes.content}>
          <div className={classes.toolbar} />
          <div>
            <Switch>
              <Route path="/all" component={Ranking} />
              <Route path="/category/1" 
                render={ () => <Redirect to="/all" /> }
              />
              <Route path="/category/:id" 
                render={ ({match}) => <Ranking categoryId={match.params.id} /> }
              />
            </Switch>
          </div>
        </main>
      </div>
    );
  }
}


ResponsiveDrawer.propTypes = {
  classes: PropTypes.object.isRequired,
  container: PropTypes.object,
  theme: PropTypes.object.isRequired,
};

export default connect() ( withStyles(styles, { withTheme: true })(ResponsiveDrawer) );

以下のようにredux-thunkのaction関数において、connected-react-routerから取り出したreplace や push で自由にhistoryにアクセスしURLを変更できることに注意してください。

src/actions/Ranking.js
import fetchJsonp from 'fetch-jsonp';
import qs from 'qs';
// connected-react-router - action経由でルーティングが可能、push,replace..
import { replace, push } from 'connected-react-router';

const API_URL = 'https://shopping.yahooapis.jp/ShoppingWebService/V1/json/categoryRanking';

const APP_ID = 'xxxxxxxxxx';

const startRequest = category => ({
  type: 'START_REQUEST',
  payload: { category },
});


const receiveData = (category, error, response) => ({
  type: 'RECEIVE_DATA',
  payload: { category, error, response },
});


const finishRequest = category => ({
  type: 'FINISH_REQUEST',
  payload: { category },
});


// ** Redux-thunk
// ** action関数をラッピング - 内部で通常のactionを使いStoreを更新
export const fetchRanking = categoryId => {
  return async (dispatch, getState) => {
    const categories = getState().shopping.categories;
    const category = categories.find(category => (category.id === categoryId));
    if(typeof category === 'undefined') {
      dispatch(replace('/')); // URLをトップに変更
      return;
    }

    // ** 通常のaction
    dispatch(startRequest(category));

    const queryString = qs.stringify({
      appid: APP_ID,
      category_id: categoryId,
    });
console.log(queryString)

    try {
      const response = await fetchJsonp(`${API_URL}?${queryString}`);
      const data = await response.json();
      // ** 通常のaction
      dispatch(receiveData(category, null, data));
    } catch (err) {
      // ** 通常のaction
      dispatch(receiveData(category, err));
    }
    // ** 通常のaction
    dispatch(finishRequest(category));

    // test
    /*****
    setTimeout( () => {
      // dispatch(replace('/'));
      dispatch(push('/'));
    }, 3000);
    *****/
  }
}

今回は以上です。

10
10
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
10
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?