diff --git a/docs/api.md b/docs/api.md
index 4082326be..85561c865 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -67,6 +67,7 @@ Instead, it *returns* a new, connected component class, for you to use.
* [`options`] *(Object)* If specified, further customizes the behavior of the connector.
* [`pure = true`] *(Boolean)*: If true, implements `shouldComponentUpdate` and shallowly compares the result of `mergeProps`, preventing unnecessary updates, assuming that the component is a “pure” component and does not rely on any input or state other than its props and the selected Redux store’s state. *Defaults to `true`.*
* [`withRef = false`] *(Boolean)*: If true, stores a ref to the wrapped component instance and makes it available via `getWrappedInstance()` method. *Defaults to `false`.*
+ * [`arePropsEqual = shallowEqual`] *(Function)*: Replaces the default equality comparison between props when determining if props have been updated. *Defaults to `shallowEqual` comparison.*
#### Returns
diff --git a/src/components/connect.js b/src/components/connect.js
index 5b31632d1..a93cc679c 100644
--- a/src/components/connect.js
+++ b/src/components/connect.js
@@ -30,7 +30,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
const finalMergeProps = mergeProps || defaultMergeProps
const shouldUpdateStateProps = finalMapStateToProps.length > 1
const shouldUpdateDispatchProps = finalMapDispatchToProps.length > 1
- const { pure = true, withRef = false } = options
+ const { pure = true, withRef = false, arePropsEqual = shallowEqual } = options
// Helps track hot reloading.
const version = nextVersion++
@@ -84,7 +84,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
}
const storeChanged = nextState.storeState !== this.state.storeState
- const propsChanged = !shallowEqual(nextProps, this.props)
+ const propsChanged = !arePropsEqual(nextProps, this.props)
let mapStateProducedChange = false
let dispatchPropsChanged = false
@@ -132,7 +132,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
updateStateProps(props = this.props) {
const nextStateProps = computeStateProps(this.store, props)
- if (shallowEqual(nextStateProps, this.stateProps)) {
+ if (arePropsEqual(nextStateProps, this.stateProps)) {
return false
}
@@ -142,7 +142,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
updateDispatchProps(props = this.props) {
const nextDispatchProps = computeDispatchProps(this.store, props)
- if (shallowEqual(nextDispatchProps, this.dispatchProps)) {
+ if (arePropsEqual(nextDispatchProps, this.dispatchProps)) {
return false
}
diff --git a/test/components/connect.spec.js b/test/components/connect.spec.js
index bae556e0a..827277feb 100644
--- a/test/components/connect.spec.js
+++ b/test/components/connect.spec.js
@@ -1369,5 +1369,57 @@ describe('React', () => {
// But render is not because it did not make any actual changes
expect(renderCalls).toBe(1)
})
+
+ it('should accept arePropsEqual option for custom equality', () => {
+ const store = createStore(stringBuilder)
+ let renderCalls = 0
+ let mapStateCalls = 0
+
+ function deeperShallowEqual(maxDepth) {
+ return function eq(objA, objB, depth = 0) {
+ if (objA === objB) return true
+ if (depth > maxDepth) return objA === objB
+ const keysA = Object.keys(objA)
+ const keysB = Object.keys(objB)
+ if (keysA.length !== keysB.length) return false
+ const hasOwn = Object.prototype.hasOwnProperty
+ for (let i = 0; i < keysA.length; i++) {
+ if (!hasOwn.call(objB, keysA[i]) ||
+ !eq(objA[keysA[i]], objB[keysA[i]], depth + 1)) {
+ return false
+ }
+ }
+ return true
+ }
+ }
+
+ @connect((state, props) => {
+ mapStateCalls++
+ return { a: [ 1, 2, 3, 4 ], name: props.name } // no change with new equality comparison!
+ }, null, null, { arePropsEqual: deeperShallowEqual(1) })
+ class Container extends Component {
+ render() {
+ renderCalls++
+ return
+ }
+ }
+
+ TestUtils.renderIntoDocument(
+
+
+
+ )
+
+ expect(renderCalls).toBe(1)
+ expect(mapStateCalls).toBe(2)
+
+ store.dispatch({ type: 'APPEND', body: 'a' })
+
+ // After store a change mapState has been called
+ expect(mapStateCalls).toBe(3)
+ // But render is not because it did not make any actual changes
+ expect(renderCalls).toBe(1)
+ })
+
})
})