cescoferraro / connected-react-router

A Redux binding for React Router v4

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Connected React Router Build Status

A Redux binding for React Router v4

Main features

✨ Synchronize router state with redux store with uni-directional flow (history -> store -> router -> components).

🎁 Support React Router v4.

β˜€οΈ Support functional component hot reloading while preserving state (with react-hot-reload v3).

πŸŽ‰ Dispatching history methods (push, replace, go, goBack, goForward) work for both redux-thunk and redux-saga.

β›„ Nested children can access routing state such as current location directly with react-redux's connect.

πŸ•˜ Support time traveling in Redux DevTools.

πŸ’Ž Support Immutable.js

Note:

  • connected-react-router@4.0.0-beta supports new react-router@4.0.0-beta (with <Route> and <Switch>)
  • For old react-router@4.0.0-alpha (with <Match> and <Miss>), you need connected-react-router@2.0.0-alpha.5

Installation

Using npm:

$ npm install --save connected-react-router

Or yarn:

$ yarn add connected-react-router

Usage

Step 1

  • Create a history object.
  • Wrap the root reducer with connectRouter and supply the history object to get a new root reducer.
  • Use routerMiddleware(history) if you want to dispatch history actions (ex. to change URL with push('/path/to/somewhere')).
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
...
const history = createBrowserHistory()

const store = createStore(
  connectRouter(history)(rootReducer), // new root reducer with router state
  initialState,
  compose(
    applyMiddleware(
      routerMiddleware(history), // for dispatching history actions
      // ... other middlewares ...
    ),
  ),
)

Step 2

  • Wrap your react-router v4 routing with ConnectedRouter and pass history object as a prop.
  • Place ConnectedRouter as children of react-redux's Provider.
...
import { Provider } from 'react-redux'
import { Route, Switch } from 'react-router' // react-router v4
import { ConnectedRouter } from 'connected-react-router'
...
ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
      <div> { /* your usual react-router v4 routing */ }
        <Switch>
          <Route exact path="/" render={() => (<div>Match</div>)} />
          <Route render={() => (<div>Miss</div>)} />
        </Switch>
      </div>
    </ConnectedRouter>
  </Provider>,
  document.getElementById('react-root')
)

Now, it's ready to work!

Examples

See examples folder

FAQ

How to navigate with Redux action

with store.dispatch

import { push } from 'connected-react-router'

store.dispatch(push('/path/to/somewhere'))

in redux thunk

import { push } from 'connected-react-router'

export const login = (username, password) => (dispatch) => {

  /* do something before redirection */

  dispatch(push('/home'))
}

in redux saga

import { push } from 'connected-react-router'
import { put, call } from 'redux-saga/effects'

export function* login(username, password) {

  /* do something before redirection */

  yield put(push('/home'))
}

How to get current browser location (URL)

The current browser location can be accessed directry from the router state with react-redux's connect. The location object composes of pathname, search (query string), and hash.

import { connect } from 'react-redux'

const Child = ({ pathname, search, hash }) => (
  <div>
    Child receives
    <div>
      pathname: {pathname}
    </div>
    <div>
      search: {search}
    </div>
    <div>
      hash: {hash}
    </div>
  </div>
)

const mapStateToProps = state => ({
  pathname: state.router.location.pathname,
  search: state.router.location.search,
  hash: state.router.location.hash,
})

export default connect(mapStateToProps)(Child)

How to hot reload functional components

  1. Separate main app component to another file.

App.js

import React from 'react'
import { Route, Switch } from 'react-router' /* react-router v4 */
import { ConnectedRouter } from 'connected-react-router'

const App = ({ history }) => ( /* receive history object via props */
  <ConnectedRouter history={history}>
    <div>
      <Switch>
        <Route exact path="/" render={() => (<div>Match</div>)} />
        <Route render={() => (<div>Miss</div>)} />
      </Switch>
    </div>
  </ConnectedRouter>
)

export default App
  1. Wrap the App component with AppContainer from react-hot-loader v3 as a top-level container.

index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { AppContainer } from 'react-hot-loader' /* react-hot-loader v3 */
import App from './App'
...
const render = () => { // this function will be reused
  ReactDOM.render(
    <AppContainer> { /* AppContainer for hot reloading v3 */ }
      <Provider store={store}>
        <AppComponent history={history} /> { /* pass history object as props */ }
      </Provider>
    </AppContainer>,
    document.getElementById('react-root')
  )
}

render()
  1. Detect change and re-render with hot reload.

index.js

...
if (module.hot) {
  module.hot.accept('./App', () => {
    /* For Webpack 2.x
       Need to disable babel ES2015 modules transformation in .babelrc
       presets: [
         ["es2015", { "modules": false }]
       ]
    */
    render()

    /* For Webpack 1.x
    const NextApp = require('./App').default
    renderWithHotReload(NextApp)
    */
  })
}

Now, when you change any component that App depends on, it will trigger hot reloading without losing redux state. Thanks react-hot-loader v3!

How to hot reload reducers

Detect change and replace with a new root reducer with router state

index.js

...
if (module.hot) {
  module.hot.accept('./reducers', () => {
    /* For Webpack 2.x
       Need to disable babel ES2015 modules transformation in .babelrc
       presets: [
         ["es2015", { "modules": false }]
       ]
    */
    store.replaceReducer(connectRouter(history)(rootReducer))

    /* For Webpack 1.x
    const nextRootReducer = require('./reducers').default
    store.replaceReducer(connectRouter(history)(nextRootReducer))
    */
  })
}

How to support Immutable.js

  1. Use combineReducers from redux-immutable to create the root reducer.
import { combineReducers } from 'redux-immutable'
...
const rootReducer = combineReducers({
  ...
})
...
  1. Import ConnectedRouter, routerMiddleware, and connectRouter from connected-react-router/immutable instead of connected-react-router.
import { ConnectedRouter, routerMiddleware, connectRouter } from 'connected-react-router/immutable'
  1. (Optional) Initialize state with Immutabel.Map()
import Immutable from 'immutable'
...
const initialState = Immutable.Map()
...
const store = createStore(
  connectRouter(history)(rootReducer),
  initialState,
  ...
)

Build

npm run build

Generated files will be in lib folder.

Contributors

See Contributors and Acknowledge.

License

MIT License

About

A Redux binding for React Router v4

License:MIT License


Languages

Language:JavaScript 100.0%