1
- import { useReducer , useRef , useEffect , useMemo , useLayoutEffect } from 'react'
1
+ import { useState , useRef , useEffect , useMemo , useLayoutEffect } from 'react'
2
2
import invariant from 'invariant'
3
3
import { useReduxContext } from './useReduxContext'
4
4
import shallowEqual from '../utils/shallowEqual'
@@ -42,52 +42,27 @@ export function useSelector(selector) {
42
42
invariant ( selector , `You must pass a selector to useSelectors` )
43
43
44
44
const { store, subscription : contextSub } = useReduxContext ( )
45
- const [ , forceRender ] = useReducer ( s => s + 1 , 0 )
46
-
47
45
const subscription = useMemo ( ( ) => new Subscription ( store , contextSub ) , [
48
46
store ,
49
47
contextSub
50
48
] )
51
49
52
- const latestSubscriptionCallbackError = useRef ( )
53
- const latestSelector = useRef ( selector )
54
-
55
- let selectedState = undefined
56
-
57
- try {
58
- selectedState = selector ( store . getState ( ) )
59
- } catch ( err ) {
60
- let errorMessage = `An error occured while selecting the store state: ${
61
- err . message
62
- } .`
50
+ const [ subscriptionResult , setSubscriptionResult ] = useState ( )
63
51
64
- if ( latestSubscriptionCallbackError . current ) {
65
- errorMessage += `\nThe error may be correlated with this previous error:\n${
66
- latestSubscriptionCallbackError . current . stack
67
- } \n\nOriginal stack trace:`
68
- }
69
-
70
- throw new Error ( errorMessage )
71
- }
72
-
73
- const latestSelectedState = useRef ( selectedState )
74
-
75
- useIsomorphicLayoutEffect ( ( ) => {
76
- latestSelector . current = selector
77
- latestSelectedState . current = selectedState
78
- latestSubscriptionCallbackError . current = undefined
79
- } )
52
+ const latestSelector = useRef ( )
53
+ const latestResult = useRef ( )
54
+ const latestSubscriptionCallbackError = useRef ( )
55
+ const latestSubscriptionResult = useRef ( )
80
56
81
57
useIsomorphicLayoutEffect ( ( ) => {
82
58
function checkForUpdates ( ) {
59
+ let newSelectedState
83
60
try {
84
- const newSelectedState = latestSelector . current ( store . getState ( ) )
61
+ newSelectedState = latestSelector . current ( store . getState ( ) )
85
62
86
- if ( shallowEqual ( newSelectedState , latestSelectedState . current ) ) {
63
+ if ( shallowEqual ( newSelectedState , latestResult . current ) ) {
87
64
return
88
65
}
89
-
90
- latestSelectedState . current = newSelectedState
91
66
} catch ( err ) {
92
67
// we ignore all errors here, since when the component
93
68
// is re-rendered, the selectors are called again, and
@@ -96,16 +71,52 @@ export function useSelector(selector) {
96
71
latestSubscriptionCallbackError . current = err
97
72
}
98
73
99
- forceRender ( { } )
74
+ const newSubscriptionResult = new Array ( 1 )
75
+ newSubscriptionResult [ 0 ] = newSelectedState
76
+ setSubscriptionResult ( newSubscriptionResult )
100
77
}
101
78
102
79
subscription . onStateChange = checkForUpdates
103
80
subscription . trySubscribe ( )
104
81
105
- checkForUpdates ( )
106
-
107
82
return ( ) => subscription . tryUnsubscribe ( )
108
83
} , [ store , subscription ] )
109
84
110
- return selectedState
85
+ let result = latestResult . current
86
+
87
+ useIsomorphicLayoutEffect ( ( ) => {
88
+ latestResult . current = result
89
+ latestSelector . current = selector
90
+ latestSubscriptionCallbackError . current = undefined
91
+ latestSubscriptionResult . current = subscriptionResult
92
+ } )
93
+
94
+ try {
95
+ return ( result =
96
+ // If the selector has changed or if there has been an error while
97
+ // it was being evaluated inside "checkForUpdates", then the selector
98
+ // has to be re-evaluated
99
+ selector !== latestSelector . current ||
100
+ latestSubscriptionCallbackError . current
101
+ ? selector ( store . getState ( ) )
102
+ : // If this update was triggered by "checkForUpdates", then we
103
+ // should return the result that was already calculated in there.
104
+ // Otherwise, this is an update triggered by an ancestor and
105
+ // there is no need to recalculate the result
106
+ subscriptionResult !== latestSubscriptionResult . current
107
+ ? subscriptionResult [ 0 ]
108
+ : result )
109
+ } catch ( err ) {
110
+ let errorMessage = `An error occured while selecting the store state: ${
111
+ err . message
112
+ } .`
113
+
114
+ if ( latestSubscriptionCallbackError . current ) {
115
+ errorMessage += `\nThe error may be correlated with this previous error:\n${
116
+ latestSubscriptionCallbackError . current . stack
117
+ } \n\nOriginal stack trace:`
118
+ }
119
+
120
+ throw new Error ( errorMessage )
121
+ }
111
122
}
0 commit comments