diff --git a/examples/real-world/containers/App.js b/examples/real-world/containers/App.js
index 5c98257f7f..f7db3cd2b8 100644
--- a/examples/real-world/containers/App.js
+++ b/examples/real-world/containers/App.js
@@ -1,6 +1,6 @@
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
-import { push } from 'react-router-redux'
+import { browserHistory } from 'react-router'
import Explore from '../components/Explore'
import { resetErrorMessage } from '../actions'
@@ -17,7 +17,7 @@ class App extends Component {
}
handleChange(nextValue) {
- this.props.push(`/${nextValue}`)
+ browserHistory.push(`/${nextValue}`)
}
renderErrorMessage() {
@@ -56,20 +56,18 @@ App.propTypes = {
// Injected by React Redux
errorMessage: PropTypes.string,
resetErrorMessage: PropTypes.func.isRequired,
- push: PropTypes.func.isRequired,
inputValue: PropTypes.string.isRequired,
// Injected by React Router
children: PropTypes.node
}
-function mapStateToProps(state) {
+function mapStateToProps(state, ownProps) {
return {
errorMessage: state.errorMessage,
- inputValue: state.routing.location.pathname.substring(1)
+ inputValue: ownProps.location.pathname.substring(1)
}
}
export default connect(mapStateToProps, {
- resetErrorMessage,
- push
+ resetErrorMessage
})(App)
diff --git a/examples/real-world/containers/RepoPage.js b/examples/real-world/containers/RepoPage.js
index c38d9cd4f2..e5e62c7c30 100644
--- a/examples/real-world/containers/RepoPage.js
+++ b/examples/real-world/containers/RepoPage.js
@@ -72,8 +72,8 @@ RepoPage.propTypes = {
loadStargazers: PropTypes.func.isRequired
}
-function mapStateToProps(state, props) {
- const { login, name } = props.params
+function mapStateToProps(state, ownProps) {
+ const { login, name } = ownProps.params
const {
pagination: { stargazersByRepo },
entities: { users, repos }
diff --git a/examples/real-world/containers/Root.dev.js b/examples/real-world/containers/Root.dev.js
index e8726bfb0a..b0d828a01a 100644
--- a/examples/real-world/containers/Root.dev.js
+++ b/examples/real-world/containers/Root.dev.js
@@ -2,15 +2,15 @@ import React, { Component, PropTypes } from 'react'
import { Provider } from 'react-redux'
import routes from '../routes'
import DevTools from './DevTools'
-import { Router, browserHistory } from 'react-router'
+import { Router } from 'react-router'
export default class Root extends Component {
render() {
- const { store } = this.props
+ const { store, history } = this.props
return (
-
+
diff --git a/examples/real-world/containers/Root.prod.js b/examples/real-world/containers/Root.prod.js
index adc514de9e..b8a363b7f1 100644
--- a/examples/real-world/containers/Root.prod.js
+++ b/examples/real-world/containers/Root.prod.js
@@ -1,14 +1,14 @@
import React, { Component, PropTypes } from 'react'
import { Provider } from 'react-redux'
import routes from '../routes'
-import { Router, browserHistory } from 'react-router'
+import { Router } from 'react-router'
export default class Root extends Component {
render() {
- const { store } = this.props
+ const { store, history } = this.props
return (
-
+
)
}
diff --git a/examples/real-world/containers/UserPage.js b/examples/real-world/containers/UserPage.js
index d5c7f8877f..29a25f0220 100644
--- a/examples/real-world/containers/UserPage.js
+++ b/examples/real-world/containers/UserPage.js
@@ -72,8 +72,8 @@ UserPage.propTypes = {
loadStarred: PropTypes.func.isRequired
}
-function mapStateToProps(state, props) {
- const { login } = props.params
+function mapStateToProps(state, ownProps) {
+ const { login } = ownProps.params
const {
pagination: { starredByUser },
entities: { users, repos }
diff --git a/examples/real-world/index.js b/examples/real-world/index.js
index 3a8de9d2ef..62c8db48f7 100644
--- a/examples/real-world/index.js
+++ b/examples/real-world/index.js
@@ -1,12 +1,17 @@
import 'babel-polyfill'
import React from 'react'
import { render } from 'react-dom'
+import { browserHistory } from 'react-router'
import Root from './containers/Root'
import configureStore from './store/configureStore'
+import { syncHistoryWithStore } from './syncHistoryWithStore'
const store = configureStore()
+const history = syncHistoryWithStore(browserHistory, store, {
+ adjustUrlOnReplay: true
+})
render(
- ,
+ ,
document.getElementById('root')
)
diff --git a/examples/real-world/package.json b/examples/real-world/package.json
index c08901d7f5..166df80c4e 100644
--- a/examples/real-world/package.json
+++ b/examples/real-world/package.json
@@ -24,7 +24,6 @@
"react-dom": "^0.14.7",
"react-redux": "^4.2.1",
"react-router": "2.0.0-rc5",
- "react-router-redux": "^2.1.0",
"redux": "^3.2.1",
"redux-logger": "^2.4.0",
"redux-thunk": "^1.0.3"
diff --git a/examples/real-world/reducers/index.js b/examples/real-world/reducers/index.js
index d45315fcb3..df80beffb3 100644
--- a/examples/real-world/reducers/index.js
+++ b/examples/real-world/reducers/index.js
@@ -1,7 +1,7 @@
import * as ActionTypes from '../actions'
import merge from 'lodash/merge'
import paginate from './paginate'
-import { routeReducer } from 'react-router-redux'
+import { reducer as routing } from '../syncHistoryWithStore'
import { combineReducers } from 'redux'
// Updates an entity cache in response to any action with response.entities.
@@ -50,8 +50,7 @@ const rootReducer = combineReducers({
entities,
pagination,
errorMessage,
- routing: routeReducer
+ routing
})
-
export default rootReducer
diff --git a/examples/real-world/store/configureStore.dev.js b/examples/real-world/store/configureStore.dev.js
index 4db7c98edd..c081a7b347 100644
--- a/examples/real-world/store/configureStore.dev.js
+++ b/examples/real-world/store/configureStore.dev.js
@@ -1,27 +1,20 @@
import { createStore, applyMiddleware, compose } from 'redux'
-import { syncHistory } from 'react-router-redux'
-import { browserHistory } from 'react-router'
-import DevTools from '../containers/DevTools'
import thunk from 'redux-thunk'
-import api from '../middleware/api'
import createLogger from 'redux-logger'
+import api from '../middleware/api'
import rootReducer from '../reducers'
-
-const reduxRouterMiddleware = syncHistory(browserHistory)
+import DevTools from '../containers/DevTools'
export default function configureStore(initialState) {
const store = createStore(
rootReducer,
initialState,
compose(
- applyMiddleware(thunk, api, reduxRouterMiddleware, createLogger()),
+ applyMiddleware(thunk, api, createLogger()),
DevTools.instrument()
)
)
- // Required for replaying actions from devtools to work
- reduxRouterMiddleware.listenForReplays(store)
-
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
diff --git a/examples/real-world/store/configureStore.prod.js b/examples/real-world/store/configureStore.prod.js
index a45088f0e0..3a9b853caf 100644
--- a/examples/real-world/store/configureStore.prod.js
+++ b/examples/real-world/store/configureStore.prod.js
@@ -1,6 +1,4 @@
import { createStore, applyMiddleware } from 'redux'
-import { syncHistory } from 'react-router-redux'
-import { browserHistory } from 'react-router'
import thunk from 'redux-thunk'
import api from '../middleware/api'
import rootReducer from '../reducers'
@@ -9,6 +7,6 @@ export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
- applyMiddleware(thunk, api, syncHistory(browserHistory))
+ applyMiddleware(thunk, api)
)
}
diff --git a/examples/real-world/syncHistoryWithStore.js b/examples/real-world/syncHistoryWithStore.js
new file mode 100644
index 0000000000..afe9de7d06
--- /dev/null
+++ b/examples/real-world/syncHistoryWithStore.js
@@ -0,0 +1,131 @@
+export const WILL_NAVIGATE = '@@react-router/WILL_NAVIGATE'
+
+const initialState = {
+ locationBeforeTransitions: null
+}
+
+// Mount this reducer to handle location changes
+export const reducer = (state = initialState, action) => {
+ switch (action.type) {
+ case WILL_NAVIGATE:
+ // Use a descriptive name to make it less tempting to reach into it
+ return {
+ locationBeforeTransitions: action.locationBeforeTransitions
+ }
+ default:
+ return state
+ }
+}
+
+export function syncHistoryWithStore(history, store, {
+ // Specify where you mounted the reducer
+ selectLocationState = state => state.routing,
+ adjustUrlOnReplay = false
+} = {}) {
+ // Fail early if the reducer is not mounted
+ if (typeof selectLocationState(store.getState()) === 'undefined') {
+ throw new Error(
+ 'Expected the routing state to be available either as `state.routing` ' +
+ 'or as the custom expression you can specify as `selectLocationState` ' +
+ 'named argument in the `syncHistoryWithStore()` options. Did you ' +
+ 'forget to put the `reducer` exported from `syncHistoryWithStore` into ' +
+ 'your `combineReducers()` call?'
+ )
+ }
+
+ let initialLocation
+ let currentLocation
+ let isTimeTraveling
+ let unsubscribeFromStore
+ let unsubscribeFromHistory
+
+ // What does the store say about current location?
+ const getLocationInStore = (useInitialIfEmpty) => {
+ const locationState = selectLocationState(store.getState())
+ return locationState.locationBeforeTransitions ||
+ (useInitialIfEmpty ? initialLocation : undefined)
+ }
+
+ // Whenever store changes due to time travel, keep address bar in sync
+ const handleStoreChange = () => {
+ const locationInStore = getLocationInStore(true)
+ if (currentLocation === locationInStore) {
+ return
+ }
+
+ // Update address bar to reflect store state
+ isTimeTraveling = true
+ currentLocation = locationInStore
+ history.transitionTo(Object.assign({},
+ locationInStore,
+ { action: 'PUSH' }
+ ))
+ isTimeTraveling = false
+ }
+
+ if (adjustUrlOnReplay) {
+ unsubscribeFromStore = store.subscribe(handleStoreChange)
+ handleStoreChange()
+ }
+
+ // Whenever location changes, dispatch an action to get it in the store
+ const handleLocationChange = (location) => {
+ // ... unless we just caused that location change
+ if (isTimeTraveling) {
+ return
+ }
+
+ // Remember where we are
+ currentLocation = location
+
+ // Are we being called for the first time?
+ if (!initialLocation) {
+ // Remember as a fallback in case state is reset
+ initialLocation = location
+
+ // Respect persisted location, if any
+ if (getLocationInStore()) {
+ return
+ }
+ }
+
+ // Tell the store to update by dispatching an action
+ store.dispatch({
+ type: WILL_NAVIGATE,
+ locationBeforeTransitions: location
+ })
+ }
+ unsubscribeFromHistory = history.listen(handleLocationChange)
+
+ // The enhanced history uses store as source of truth
+ return Object.assign({}, history, {
+ // The listeners are subscribed to the store instead of history
+ listen(listener) {
+ // History listeners expect a synchronous call
+ listener(getLocationInStore(true))
+
+ // Keep track of whether we unsubscribed, as Redux store
+ // only applies changes in subscriptions on next dispatch
+ let unsubscribed = false
+ const unsubscribeFromStore = store.subscribe(() => {
+ if (!unsubscribed) {
+ listener(getLocationInStore(true))
+ }
+ })
+
+ // Let user unsubscribe later
+ return () => {
+ unsubscribed = true
+ unsubscribeFromStore()
+ }
+ },
+
+ // It also provides a way to destroy internal listeners
+ dispose() {
+ if (adjustUrlOnReplay) {
+ unsubscribeFromStore()
+ }
+ unsubscribeFromHistory()
+ }
+ })
+}