diff --git a/package.json b/package.json
index 2eddc2c8..3044a7e1 100644
--- a/package.json
+++ b/package.json
@@ -31,8 +31,9 @@
"prop-types": "^15.7.2"
},
"peerDependencies": {
- "immutable": "^3.8.1 || ^4.0.0-rc.1",
"history": "^4.7.2",
+ "immutable": "^3.8.1 || ^4.0.0-rc.1",
+ "lodash.isequalwith": "^4.4.0",
"react": "^16.4.0",
"react-redux": "^6.0.0 || ^7.1.0",
"react-router": "^4.3.1 || ^5.0.0",
diff --git a/src/ConnectedRouter.js b/src/ConnectedRouter.js
index 58d1426b..a2957634 100644
--- a/src/ConnectedRouter.js
+++ b/src/ConnectedRouter.js
@@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { connect, ReactReduxContext } from 'react-redux'
import { Router } from 'react-router'
+import isEqualWith from 'lodash.isequalwith'
import { onLocationChanged } from './actions'
import createSelectors from './selectors'
@@ -18,7 +19,7 @@ const createConnectedRouter = (structure) => {
constructor(props) {
super(props)
- const { store, history, onLocationChanged } = props
+ const { store, history, onLocationChanged, stateCompareFunction } = props
this.inTimeTravelling = false
@@ -45,7 +46,7 @@ const createConnectedRouter = (structure) => {
(pathnameInHistory !== pathnameInStore ||
searchInHistory !== searchInStore ||
hashInHistory !== hashInStore ||
- stateInStore !== stateInHistory)
+ !isEqualWith(stateInStore, stateInHistory, stateCompareFunction))
) {
this.inTimeTravelling = true
// Update history's location to match store's location
@@ -109,6 +110,7 @@ const createConnectedRouter = (structure) => {
children: PropTypes.oneOfType([ PropTypes.func, PropTypes.node ]),
onLocationChanged: PropTypes.func.isRequired,
noInitialPop: PropTypes.bool,
+ stateCompareFunction: PropTypes.func,
}
const mapDispatchToProps = dispatch => ({
diff --git a/test/ConnectedRouter.test.js b/test/ConnectedRouter.test.js
index 9c15ccd5..3deb5103 100644
--- a/test/ConnectedRouter.test.js
+++ b/test/ConnectedRouter.test.js
@@ -9,7 +9,7 @@ import { createMemoryHistory } from 'history'
import { Route } from 'react-router'
import { Provider } from 'react-redux'
import createConnectedRouter from '../src/ConnectedRouter'
-import { onLocationChanged } from '../src/actions'
+import { onLocationChanged, LOCATION_CHANGE } from '../src/actions'
import plainStructure from '../src/structure/plain'
import immutableStructure from '../src/structure/immutable'
import seamlessImmutableStructure from '../src/structure/seamless-immutable'
@@ -136,6 +136,170 @@ describe('ConnectedRouter', () => {
expect(onLocationChangedSpy.mock.calls[1][0].state).toEqual({ foo: 'bar'})
})
+ it('updates history when store location state changes', () => {
+ store = createStore(
+ combineReducers({
+ router: connectRouter(props.history)
+ }),
+ compose(applyMiddleware(routerMiddleware(props.history)))
+ )
+
+ mount(
+
+
+ Home
} />
+
+
+ )
+
+ // Need to add PUSH action to history because initial POP action prevents history updates
+ props.history.push({ pathname: "/" })
+
+ store.dispatch({
+ type: LOCATION_CHANGE,
+ payload: {
+ location: {
+ pathname: '/',
+ search: '',
+ hash: '',
+ state: { foo: 'bar' }
+ },
+ action: 'PUSH',
+ }
+ })
+
+ expect(props.history.entries).toHaveLength(3)
+
+ store.dispatch({
+ type: LOCATION_CHANGE,
+ payload: {
+ location: {
+ pathname: '/',
+ search: '',
+ hash: '',
+ state: { foo: 'baz' }
+ },
+ action: 'PUSH',
+ }
+ })
+
+ expect(props.history.entries).toHaveLength(4)
+ })
+
+ it('does not update history when store location state is unchanged', () => {
+ store = createStore(
+ combineReducers({
+ router: connectRouter(props.history)
+ }),
+ compose(applyMiddleware(routerMiddleware(props.history)))
+ )
+
+ mount(
+
+
+ Home
} />
+
+
+ )
+
+ // Need to add PUSH action to history because initial POP action prevents history updates
+ props.history.push({ pathname: "/" })
+
+ store.dispatch({
+ type: LOCATION_CHANGE,
+ payload: {
+ location: {
+ pathname: '/',
+ search: '',
+ hash: '',
+ state: { foo: 'bar' }
+ },
+ action: 'PUSH',
+ }
+ })
+
+ expect(props.history.entries).toHaveLength(3)
+
+ store.dispatch({
+ type: LOCATION_CHANGE,
+ payload: {
+ location: {
+ pathname: '/',
+ search: '',
+ hash: '',
+ state: { foo: 'bar' }
+ },
+ action: 'PUSH',
+ }
+ })
+
+ expect(props.history.entries).toHaveLength(3)
+ })
+
+ it('supports custom location state compare function', () => {
+ store = createStore(
+ combineReducers({
+ router: connectRouter(props.history)
+ }),
+ compose(applyMiddleware(routerMiddleware(props.history)))
+ )
+
+ mount(
+
+ {
+ // If the store and history states are not undefined,
+ // prevent history from updating when 'baz' is added to the store after 'bar'
+ if (storeState !== undefined && historyState !== undefined) {
+ if (storeState.foo === "baz" && historyState.foo === 'bar') {
+ return true
+ }
+ }
+
+ // Otherwise return a normal object comparison result
+ return JSON.stringify(storeState) === JSON.stringify(historyState)
+ }}
+ {...props}
+ >
+ Home
} />
+
+
+ )
+
+ // Need to add PUSH action to history because initial POP action prevents history updates
+ props.history.push({ pathname: "/" })
+
+ store.dispatch({
+ type: LOCATION_CHANGE,
+ payload: {
+ location: {
+ pathname: '/',
+ search: '',
+ hash: '',
+ state: { foo: 'bar' }
+ },
+ action: 'PUSH',
+ }
+ })
+
+ expect(props.history.entries).toHaveLength(3)
+
+ store.dispatch({
+ type: LOCATION_CHANGE,
+ payload: {
+ location: {
+ pathname: '/',
+ search: '',
+ hash: '',
+ state: { foo: 'baz' }
+ },
+ action: 'PUSH',
+ }
+ })
+
+ expect(props.history.entries).toHaveLength(3)
+ })
+
it('only renders one time when mounted', () => {
let renderCount = 0
diff --git a/yarn.lock b/yarn.lock
index 78853479..d889e586 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4733,6 +4733,11 @@ lodash.flattendeep@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
+lodash.isequalwith@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz#266726ddd528f854f21f4ea98a065606e0fbc6b0"
+ integrity sha1-Jmcm3dUo+FTyH06pigZWBuD7xrA=
+
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"