Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace shallowEqual with reference equality in useSelector #1288

Merged
merged 2 commits into from
May 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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