Skip to content

Commit 8a673c9

Browse files
perrin4869markerikson
authored andcommitted
Replace shallowEqual with reference equality in useSelector (#1288)
* Replace shallowEqual with reference equality in useSelector * useSelector: Allow optional compararison function Export shallowEqual function
1 parent a787aee commit 8a673c9

File tree

4 files changed

+39
-9
lines changed

4 files changed

+39
-9
lines changed

package-lock.json

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hooks/useSelector.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useReducer, useRef, useEffect, useMemo, useLayoutEffect } from 'react'
22
import invariant from 'invariant'
33
import { useReduxContext } from './useReduxContext'
4-
import shallowEqual from '../utils/shallowEqual'
54
import Subscription from '../utils/Subscription'
65

76
// React currently throws a warning when using useLayoutEffect on the server.
@@ -15,6 +14,8 @@ import Subscription from '../utils/Subscription'
1514
const useIsomorphicLayoutEffect =
1615
typeof window !== 'undefined' ? useLayoutEffect : useEffect
1716

17+
const refEquality = (a, b) => a === b
18+
1819
/**
1920
* A hook to access the redux store's state. This hook takes a selector function
2021
* as an argument. The selector is called with the store state.
@@ -24,6 +25,7 @@ const useIsomorphicLayoutEffect =
2425
* useful if you provide a selector that memoizes values).
2526
*
2627
* @param {Function} selector the selector function
28+
* @param {Function} equalityFn the function that will be used to determine equality
2729
*
2830
* @returns {any} the selected state
2931
*
@@ -38,7 +40,7 @@ const useIsomorphicLayoutEffect =
3840
* return <div>{counter}</div>
3941
* }
4042
*/
41-
export function useSelector(selector) {
43+
export function useSelector(selector, equalityFn = refEquality) {
4244
invariant(selector, `You must pass a selector to useSelectors`)
4345

4446
const { store, subscription: contextSub } = useReduxContext()
@@ -83,7 +85,7 @@ export function useSelector(selector) {
8385
try {
8486
const newSelectedState = latestSelector.current(store.getState())
8587

86-
if (shallowEqual(newSelectedState, latestSelectedState.current)) {
88+
if (equalityFn(newSelectedState, latestSelectedState.current)) {
8789
return
8890
}
8991

src/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useStore } from './hooks/useStore'
99

1010
import { setBatch } from './utils/batch'
1111
import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates'
12+
import shallowEqual from './utils/shallowEqual'
1213

1314
setBatch(batch)
1415

@@ -20,5 +21,6 @@ export {
2021
batch,
2122
useDispatch,
2223
useSelector,
23-
useStore
24+
useStore,
25+
shallowEqual
2426
}

test/hooks/useSelector.spec.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import React from 'react'
44
import { createStore } from 'redux'
55
import { renderHook, act } from 'react-hooks-testing-library'
66
import * as rtl from 'react-testing-library'
7-
import { Provider as ProviderMock, useSelector } from '../../src/index.js'
7+
import {
8+
Provider as ProviderMock,
9+
useSelector,
10+
shallowEqual
11+
} from '../../src/index.js'
812
import { useReduxContext } from '../../src/hooks/useReduxContext'
913

1014
describe('React', () => {
@@ -128,7 +132,30 @@ describe('React', () => {
128132
})
129133

130134
describe('performance optimizations and bail-outs', () => {
131-
it('should shallowly compare the selected state to prevent unnecessary updates', () => {
135+
it('defaults to ref-equality to prevent unnecessary updates', () => {
136+
const state = {}
137+
store = createStore(() => state)
138+
139+
const Comp = () => {
140+
const value = useSelector(s => s)
141+
renderedItems.push(value)
142+
return <div />
143+
}
144+
145+
rtl.render(
146+
<ProviderMock store={store}>
147+
<Comp />
148+
</ProviderMock>
149+
)
150+
151+
expect(renderedItems.length).toBe(1)
152+
153+
store.dispatch({ type: '' })
154+
155+
expect(renderedItems.length).toBe(1)
156+
})
157+
158+
it('allows other equality functions to prevent unnecessary updates', () => {
132159
store = createStore(
133160
({ count, stable } = { count: -1, stable: {} }) => ({
134161
count: count + 1,
@@ -137,7 +164,7 @@ describe('React', () => {
137164
)
138165

139166
const Comp = () => {
140-
const value = useSelector(s => Object.keys(s))
167+
const value = useSelector(s => Object.keys(s), shallowEqual)
141168
renderedItems.push(value)
142169
return <div />
143170
}

0 commit comments

Comments
 (0)