- How to navigate with Redux action
- How to get the current browser location (URL)
- How to set Router props e.g. basename, initialEntries, etc.
- How to hot reload functional components
- How to hot reload reducers
- How to support Immutable.js
- How to migrate from v4 to v5/v6
- How to use your own context with react-redux
import { push } from 'connected-react-router'
store.dispatch(push('/path/to/somewhere'))
import { push } from 'connected-react-router'
// in component render:
<div onClick={() => {
/** do something before redirection */
props.push('/home');
}}>login</div>
// connect the action:
export default connect(null, { push })(Component);
import { push } from 'connected-react-router'
export const login = (username, password) => (dispatch) => {
/* do something before redirection */
dispatch(push('/home'))
}
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'))
}
The current browser location can be accessed directly from the router state with react-redux
's connect
.
The location object is comprised 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)
You can pass props to the create*History
functions of your choice (createBrowserHistory
, createHashHistory
, createMemoryHistory
)
import { createBrowserHistory } from 'history'
const history = createBrowserHistory({
basename: '/prefix/',
})
import { createHashHistory } from 'history'
const history = createHashHistory({
hashType: 'slash',
getUserConfirmation: (message, callback) => callback(window.confirm(message))
})
import { createMemoryHistory } from 'history'
const history = createMemoryHistory({
initialEntries: [ '/one', '/two', { pathname: '/three' } ],
initialIndex: 1
})
- Save the main app component in its own file.
App.js
import React from 'react'
import { Route, Switch } from 'react-router' /* react-router v4/v5 */
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
- Wrap the
App
component withAppContainer
fromreact-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}>
<App history={history} /> { /* pass history object as props */ }
</Provider>
</AppContainer>,
document.getElementById('react-root')
)
}
render()
- 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!
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(rootReducer(history))
/* For Webpack 1.x
const nextRootReducer = require('./reducers').default
store.replaceReducer(nextRootReducer(history))
*/
})
}
-
Create your root reducer as a function that takes
history
and returns reducer. UsecombineReducers
fromredux-immutable
to return the root reducer. -
Import
connectRouter
fromconnected-react-router/immutable
and add router reducer to root reducer
import { combineReducers } from 'redux-immutable'
import { connectRouter } from 'connected-react-router/immutable'
...
const rootReducer = (history) => combineReducers({
router: connectRouter(history),
...
})
...
- Import
ConnectedRouter
androuterMiddleware
fromconnected-react-router/immutable
instead ofconnected-react-router
.
import { ConnectedRouter, routerMiddleware } from 'connected-react-router/immutable'
- Create your root reducer with router reducer by passing
history
torootReducer
function
const store = createStore(
rootReducer(history),
initialState,
...
)
- (Optional) Initialize state with
Immutable.Map()
import Immutable from 'immutable'
...
const initialState = Immutable.Map()
...
const store = createStore(
rootReducer(history),
initialState,
...
)
It's easy to migrate from v4 to v5/v6.
- In your root reducer file, instead of exporting a root reducer, you need to export a function accepting a
history
object and returning a root reducer withrouter
key. The value of therouter
key isconnectedRouter(history)
.
// reducers.js
import { combineReducers } from 'redux'
+ import { connectRouter } from 'connected-react-router'
- export default combineReducers({
+ export default (history) => combineReducers({
+ router: connectRouter(history),
...
})
- In
createStore
function, change to use the new function creating a root reducer.
// configureStore.js
...
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
- import { connectRouter, routerMiddleware } from 'connected-react-router'
+ import { routerMiddleware } from 'connected-react-router'
- import rootReducer from './reducers'
+ import createRootReducer from './reducers'
const history = createBrowserHistory()
const store = createStore(
- connectRouter(history)(rootReducer),
+ createRootReducer(history),
initialState,
compose(
applyMiddleware(
routerMiddleware(history),
),
),
)
- For reducers hot reloading, similarly, change to use the new function creating a root reducer.
// For Webpack 2.x
- store.replaceReducer(connectRouter(history)(rootReducer))
+ store.replaceReducer(createRootReducer(history))
// For Webpack 1.x
- const nextRootReducer = require('./reducers').default
- store.replaceReducer(connectRouter(history)(nextRootReducer))
+ const nextCreateRootReducer = require('./reducers').default
+ store.replaceReducer(nextCreateRootReducer(history))
With react-redux v6.0.0, you can pass your own context to <Provider>
component. So, you need to pass the same context as props to <ConnectedRouter>
component.
const customContext = React.createContext(null) // your own context
ReactDOM.render(
<Provider store={store} context={customContext}>
<ConnectedRouter history={history} context={customContext}>
...
</ConnectedRouter>
</Provider>
)