Skip to content

Commit d193981

Browse files
committed
options.arePropsEqual for custom prop equality comparison
1 parent 777652d commit d193981

File tree

3 files changed

+57
-4
lines changed

3 files changed

+57
-4
lines changed

docs/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ Instead, it *returns* a new, connected component class, for you to use.
6767
* [`options`] *(Object)* If specified, further customizes the behavior of the connector.
6868
* [`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`.*
6969
* [`withRef = false`] *(Boolean)*: If true, stores a ref to the wrapped component instance and makes it available via `getWrappedInstance()` method. *Defaults to `false`.*
70+
* [`arePropsEqual = shallowEqual`] *(Function)*: Replaces the default equality comparison between props when determining if props have been updated. *Defaults to `shallowEqual` comparison.*
7071

7172
#### Returns
7273

src/components/connect.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
3030
const finalMergeProps = mergeProps || defaultMergeProps
3131
const shouldUpdateStateProps = finalMapStateToProps.length > 1
3232
const shouldUpdateDispatchProps = finalMapDispatchToProps.length > 1
33-
const { pure = true, withRef = false } = options
33+
const { pure = true, withRef = false, arePropsEqual = shallowEqual } = options
3434

3535
// Helps track hot reloading.
3636
const version = nextVersion++
@@ -84,7 +84,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
8484
}
8585

8686
const storeChanged = nextState.storeState !== this.state.storeState
87-
const propsChanged = !shallowEqual(nextProps, this.props)
87+
const propsChanged = !arePropsEqual(nextProps, this.props)
8888
let mapStateProducedChange = false
8989
let dispatchPropsChanged = false
9090

@@ -132,7 +132,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
132132

133133
updateStateProps(props = this.props) {
134134
const nextStateProps = computeStateProps(this.store, props)
135-
if (shallowEqual(nextStateProps, this.stateProps)) {
135+
if (arePropsEqual(nextStateProps, this.stateProps)) {
136136
return false
137137
}
138138

@@ -142,7 +142,7 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
142142

143143
updateDispatchProps(props = this.props) {
144144
const nextDispatchProps = computeDispatchProps(this.store, props)
145-
if (shallowEqual(nextDispatchProps, this.dispatchProps)) {
145+
if (arePropsEqual(nextDispatchProps, this.dispatchProps)) {
146146
return false
147147
}
148148

test/components/connect.spec.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,5 +1369,57 @@ describe('React', () => {
13691369
// But render is not because it did not make any actual changes
13701370
expect(renderCalls).toBe(1)
13711371
})
1372+
1373+
it('should accept arePropsEqual option for custom equality', () => {
1374+
const store = createStore(stringBuilder)
1375+
let renderCalls = 0
1376+
let mapStateCalls = 0
1377+
1378+
function deeperShallowEqual(maxDepth) {
1379+
return function eq(objA, objB, depth = 0) {
1380+
if (objA === objB) return true
1381+
if (depth > maxDepth) return objA === objB
1382+
const keysA = Object.keys(objA)
1383+
const keysB = Object.keys(objB)
1384+
if (keysA.length !== keysB.length) return false
1385+
const hasOwn = Object.prototype.hasOwnProperty
1386+
for (let i = 0; i < keysA.length; i++) {
1387+
if (!hasOwn.call(objB, keysA[i]) ||
1388+
!eq(objA[keysA[i]], objB[keysA[i]], depth + 1)) {
1389+
return false
1390+
}
1391+
}
1392+
return true
1393+
}
1394+
}
1395+
1396+
@connect((state, props) => {
1397+
mapStateCalls++
1398+
return { a: [ 1, 2, 3, 4 ], name: props.name } // no change with new equality comparison!
1399+
}, null, null, { arePropsEqual: deeperShallowEqual(1) })
1400+
class Container extends Component {
1401+
render() {
1402+
renderCalls++
1403+
return <Passthrough {...this.props} />
1404+
}
1405+
}
1406+
1407+
TestUtils.renderIntoDocument(
1408+
<ProviderMock store={store}>
1409+
<Container name="test" />
1410+
</ProviderMock>
1411+
)
1412+
1413+
expect(renderCalls).toBe(1)
1414+
expect(mapStateCalls).toBe(2)
1415+
1416+
store.dispatch({ type: 'APPEND', body: 'a' })
1417+
1418+
// After store a change mapState has been called
1419+
expect(mapStateCalls).toBe(3)
1420+
// But render is not because it did not make any actual changes
1421+
expect(renderCalls).toBe(1)
1422+
})
1423+
13721424
})
13731425
})

0 commit comments

Comments
 (0)