diff --git a/package-lock.json b/package-lock.json index 83be93f56..0cbd3105d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5010,8 +5010,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "optional": true + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", diff --git a/src/hooks/useSelector.js b/src/hooks/useSelector.js index 8e40cfa10..8f331f9cd 100644 --- a/src/hooks/useSelector.js +++ b/src/hooks/useSelector.js @@ -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. @@ -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. @@ -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 * @@ -38,7 +40,7 @@ const useIsomorphicLayoutEffect = * return
{counter}
* } */ -export function useSelector(selector) { +export function useSelector(selector, equalityFn = refEquality) { invariant(selector, `You must pass a selector to useSelectors`) const { store, subscription: contextSub } = useReduxContext() @@ -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 } diff --git a/src/index.js b/src/index.js index 654d5e7fd..8817a27aa 100644 --- a/src/index.js +++ b/src/index.js @@ -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) @@ -20,5 +21,6 @@ export { batch, useDispatch, useSelector, - useStore + useStore, + shallowEqual } diff --git a/test/hooks/useSelector.spec.js b/test/hooks/useSelector.spec.js index c37c5b6e6..8f9409234 100644 --- a/test/hooks/useSelector.spec.js +++ b/test/hooks/useSelector.spec.js @@ -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', () => { @@ -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
+ } + + rtl.render( + + + + ) + + 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, @@ -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
}