Skip to content

Commit 76a1d08

Browse files
committed
Fix useSelector tearing in Concurrent Mode
1 parent cff554d commit 76a1d08

File tree

1 file changed

+47
-39
lines changed

1 file changed

+47
-39
lines changed

src/hooks/useSelector.js

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { useReducer, useRef, useMemo, useContext } from 'react'
1+
import { useState, useEffect, useMemo, useContext } from 'react'
22
import { useReduxContext as useDefaultReduxContext } from './useReduxContext'
33
import Subscription from '../utils/Subscription'
4-
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
54
import { ReactReduxContext } from '../components/Context'
65

76
const refEquality = (a, b) => a === b
@@ -12,61 +11,70 @@ function useSelectorWithStoreAndSubscription(
1211
store,
1312
contextSub
1413
) {
15-
const [, forceRender] = useReducer(s => s + 1, 0)
14+
const [state, setState] = useState(() => ({
15+
storeState: store.getState(),
16+
selector,
17+
selectedState: selector(store.getState())
18+
// subscriptionCallbackError: undefined
19+
}))
1620

1721
const subscription = useMemo(() => new Subscription(store, contextSub), [
1822
store,
1923
contextSub
2024
])
2125

22-
const latestSubscriptionCallbackError = useRef()
23-
const latestSelector = useRef()
24-
const latestSelectedState = useRef()
25-
26-
let selectedState
26+
let selectedState = state.selectedState
2727

2828
try {
29-
if (
30-
selector !== latestSelector.current ||
31-
latestSubscriptionCallbackError.current
32-
) {
33-
selectedState = selector(store.getState())
34-
} else {
35-
selectedState = latestSelectedState.current
29+
if (selector !== state.selector || state.subscriptionCallbackError) {
30+
const newSelectedState = selector(state.storeState)
31+
if (!equalityFn(newSelectedState, selectedState)) {
32+
selectedState = newSelectedState
33+
// schedule another update
34+
setState(prevState => ({
35+
...prevState,
36+
selector,
37+
selectedState
38+
// subscriptionCallbackError: undefined
39+
}))
40+
}
3641
}
3742
} catch (err) {
38-
if (latestSubscriptionCallbackError.current) {
39-
err.message += `\nThe error may be correlated with this previous error:\n${latestSubscriptionCallbackError.current.stack}\n\n`
43+
if (state.subscriptionCallbackError) {
44+
err.message += `\nThe error may be correlated with this previous error:\n${state.subscriptionCallbackError.stack}\n\n`
4045
}
4146

4247
throw err
4348
}
4449

45-
useIsomorphicLayoutEffect(() => {
46-
latestSelector.current = selector
47-
latestSelectedState.current = selectedState
48-
latestSubscriptionCallbackError.current = undefined
49-
})
50-
51-
useIsomorphicLayoutEffect(() => {
50+
useEffect(() => {
5251
function checkForUpdates() {
53-
try {
54-
const newSelectedState = latestSelector.current(store.getState())
55-
56-
if (equalityFn(newSelectedState, latestSelectedState.current)) {
57-
return
52+
const newStoreState = store.getState()
53+
setState(prevState => {
54+
let newSelectedState
55+
let subscriptionCallbackError
56+
try {
57+
newSelectedState = prevState.selector(newStoreState)
58+
59+
if (equalityFn(newSelectedState, prevState.selectedState)) {
60+
// bail out rendering
61+
return prevState
62+
}
63+
} catch (err) {
64+
// we ignore all errors here, since when the component
65+
// is re-rendered, the selectors are called again, and
66+
// will throw again, if neither props nor store state
67+
// changed
68+
subscriptionCallbackError = err
5869
}
5970

60-
latestSelectedState.current = newSelectedState
61-
} catch (err) {
62-
// we ignore all errors here, since when the component
63-
// is re-rendered, the selectors are called again, and
64-
// will throw again, if neither props nor store state
65-
// changed
66-
latestSubscriptionCallbackError.current = err
67-
}
68-
69-
forceRender({})
71+
return {
72+
...prevState,
73+
storeState: newStoreState,
74+
selectedState: newSelectedState,
75+
subscriptionCallbackError
76+
}
77+
})
7078
}
7179

7280
subscription.onStateChange = checkForUpdates

0 commit comments

Comments
 (0)