Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 007b5c7

Browse files
josepotmarkerikson
authored andcommittedMay 19, 2019
Avoid unnecessary selector evaluations (#1273)
* Avoid unnecessary selector evaluations * Clean up state assignment logic * Add missing shallowEqual export
1 parent 8a673c9 commit 007b5c7

File tree

3 files changed

+71
-7
lines changed

3 files changed

+71
-7
lines changed
 

‎src/alternate-renderers.js

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

1010
import { getBatch } from './utils/batch'
11+
import shallowEqual from './utils/shallowEqual'
1112

1213
// For other renderers besides ReactDOM and React Native, use the default noop batch function
1314
const batch = getBatch()
@@ -20,5 +21,6 @@ export {
2021
batch,
2122
useDispatch,
2223
useSelector,
23-
useStore
24+
useStore,
25+
shallowEqual
2426
}

‎src/hooks/useSelector.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,20 @@ export function useSelector(selector, equalityFn = refEquality) {
5252
])
5353

5454
const latestSubscriptionCallbackError = useRef()
55-
const latestSelector = useRef(selector)
55+
const latestSelector = useRef()
56+
const latestSelectedState = useRef()
5657

57-
let selectedState = undefined
58+
let selectedState
5859

5960
try {
60-
selectedState = selector(store.getState())
61+
if (
62+
selector !== latestSelector.current ||
63+
latestSubscriptionCallbackError.current
64+
) {
65+
selectedState = selector(store.getState())
66+
} else {
67+
selectedState = latestSelectedState.current
68+
}
6169
} catch (err) {
6270
let errorMessage = `An error occured while selecting the store state: ${
6371
err.message
@@ -72,8 +80,6 @@ export function useSelector(selector, equalityFn = refEquality) {
7280
throw new Error(errorMessage)
7381
}
7482

75-
const latestSelectedState = useRef(selectedState)
76-
7783
useIsomorphicLayoutEffect(() => {
7884
latestSelector.current = selector
7985
latestSelectedState.current = selectedState

‎test/hooks/useSelector.spec.js

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*eslint-disable react/prop-types*/
22

3-
import React from 'react'
3+
import React, { useCallback, useReducer } from 'react'
44
import { createStore } from 'redux'
55
import { renderHook, act } from 'react-hooks-testing-library'
66
import * as rtl from 'react-testing-library'
@@ -51,6 +51,29 @@ describe('React', () => {
5151
})
5252

5353
describe('lifeycle interactions', () => {
54+
it('always uses the latest state', () => {
55+
store = createStore(c => c + 1, -1)
56+
57+
const Comp = () => {
58+
const selector = useCallback(c => c + 1, [])
59+
const value = useSelector(selector)
60+
renderedItems.push(value)
61+
return <div />
62+
}
63+
64+
rtl.render(
65+
<ProviderMock store={store}>
66+
<Comp />
67+
</ProviderMock>
68+
)
69+
70+
expect(renderedItems).toEqual([1])
71+
72+
store.dispatch({ type: '' })
73+
74+
expect(renderedItems).toEqual([1, 2])
75+
})
76+
5477
it('subscribes to the store synchronously', () => {
5578
let rootSubscription
5679

@@ -183,6 +206,39 @@ describe('React', () => {
183206
})
184207
})
185208

209+
it('uses the latest selector', () => {
210+
let selectorId = 0
211+
let forceRender
212+
213+
const Comp = () => {
214+
const [, f] = useReducer(c => c + 1, 0)
215+
forceRender = f
216+
const renderedSelectorId = selectorId++
217+
const value = useSelector(() => renderedSelectorId)
218+
renderedItems.push(value)
219+
return <div />
220+
}
221+
222+
rtl.render(
223+
<ProviderMock store={store}>
224+
<Comp />
225+
</ProviderMock>
226+
)
227+
228+
expect(renderedItems).toEqual([0])
229+
230+
rtl.act(forceRender)
231+
expect(renderedItems).toEqual([0, 1])
232+
233+
rtl.act(() => {
234+
store.dispatch({ type: '' })
235+
})
236+
expect(renderedItems).toEqual([0, 1])
237+
238+
rtl.act(forceRender)
239+
expect(renderedItems).toEqual([0, 1, 2])
240+
})
241+
186242
describe('edge cases', () => {
187243
it('ignores transient errors in selector (e.g. due to stale props)', () => {
188244
const spy = jest.spyOn(console, 'error').mockImplementation(() => {})

0 commit comments

Comments
 (0)
Please sign in to comment.