Skip to content

Commit

Permalink
Replace shallowEqual with reference equality in useSelector (#1288)
Browse files Browse the repository at this point in the history
* Replace shallowEqual with reference equality in useSelector

* useSelector: Allow optional compararison function
Export shallowEqual function
  • Loading branch information
perrin4869 authored and timdorr committed May 30, 2019
1 parent c8c53f1 commit 9471f9d
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 9 deletions.
3 changes: 1 addition & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions src/hooks/useSelector.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useReducer, useRef, useEffect, useMemo, useLayoutEffect } from 'react'
import invariant from 'invariant'
import { useReduxContext } from './useReduxContext'
import shallowEqual from '../utils/shallowEqual'
import Subscription from '../utils/Subscription'

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

const refEquality = (a, b) => a === b

/**
* A hook to access the redux store's state. This hook takes a selector function
* as an argument. The selector is called with the store state.
Expand All @@ -24,6 +25,7 @@ const useIsomorphicLayoutEffect =
* useful if you provide a selector that memoizes values).
*
* @param {Function} selector the selector function
* @param {Function} equalityFn the function that will be used to determine equality
*
* @returns {any} the selected state
*
Expand All @@ -38,7 +40,7 @@ const useIsomorphicLayoutEffect =
* return <div>{counter}</div>
* }
*/
export function useSelector(selector) {
export function useSelector(selector, equalityFn = refEquality) {
invariant(selector, `You must pass a selector to useSelectors`)

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

if (shallowEqual(newSelectedState, latestSelectedState.current)) {
if (equalityFn(newSelectedState, latestSelectedState.current)) {
return
}

Expand Down
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useStore } from './hooks/useStore'

import { setBatch } from './utils/batch'
import { unstable_batchedUpdates as batch } from './utils/reactBatchedUpdates'
import shallowEqual from './utils/shallowEqual'

setBatch(batch)

Expand All @@ -20,5 +21,6 @@ export {
batch,
useDispatch,
useSelector,
useStore
useStore,
shallowEqual
}
33 changes: 30 additions & 3 deletions test/hooks/useSelector.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import React from 'react'
import { createStore } from 'redux'
import { renderHook, act } from 'react-hooks-testing-library'
import * as rtl from 'react-testing-library'
import { Provider as ProviderMock, useSelector } from '../../src/index.js'
import {
Provider as ProviderMock,
useSelector,
shallowEqual
} from '../../src/index.js'
import { useReduxContext } from '../../src/hooks/useReduxContext'

describe('React', () => {
Expand Down Expand Up @@ -128,7 +132,30 @@ describe('React', () => {
})

describe('performance optimizations and bail-outs', () => {
it('should shallowly compare the selected state to prevent unnecessary updates', () => {
it('defaults to ref-equality to prevent unnecessary updates', () => {
const state = {}
store = createStore(() => state)

const Comp = () => {
const value = useSelector(s => s)
renderedItems.push(value)
return <div />
}

rtl.render(
<ProviderMock store={store}>
<Comp />
</ProviderMock>
)

expect(renderedItems.length).toBe(1)

store.dispatch({ type: '' })

expect(renderedItems.length).toBe(1)
})

it('allows other equality functions to prevent unnecessary updates', () => {
store = createStore(
({ count, stable } = { count: -1, stable: {} }) => ({
count: count + 1,
Expand All @@ -137,7 +164,7 @@ describe('React', () => {
)

const Comp = () => {
const value = useSelector(s => Object.keys(s))
const value = useSelector(s => Object.keys(s), shallowEqual)
renderedItems.push(value)
return <div />
}
Expand Down

0 comments on commit 9471f9d

Please sign in to comment.