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,37 @@ 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 )
45
+ const [ subscriptionResult , setSubscriptionResult ] = useState ( [ null ] )
46
46
47
47
const subscription = useMemo ( ( ) => new Subscription ( store , contextSub ) , [
48
48
store ,
49
49
contextSub
50
50
] )
51
51
52
+ const latestSelector = useRef ( )
53
+ const latestResult = useRef ( )
52
54
const latestSubscriptionCallbackError = useRef ( )
53
- const latestSelector = useRef ( selector )
55
+ const latestSubscriptionResult = useRef ( subscriptionResult )
54
56
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
- } .`
63
-
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 )
57
+ let result = latestResult . current
74
58
75
59
useIsomorphicLayoutEffect ( ( ) => {
60
+ latestResult . current = result
76
61
latestSelector . current = selector
77
- latestSelectedState . current = selectedState
78
62
latestSubscriptionCallbackError . current = undefined
63
+ latestSubscriptionResult . current = subscriptionResult
79
64
} )
80
65
81
66
useIsomorphicLayoutEffect ( ( ) => {
82
67
function checkForUpdates ( ) {
83
68
try {
84
69
const newSelectedState = latestSelector . current ( store . getState ( ) )
85
70
86
- if ( shallowEqual ( newSelectedState , latestSelectedState . current ) ) {
71
+ if ( shallowEqual ( newSelectedState , latestResult . current ) ) {
87
72
return
88
73
}
89
74
90
- latestSelectedState . current = newSelectedState
75
+ latestResult . current = newSelectedState
91
76
} catch ( err ) {
92
77
// we ignore all errors here, since when the component
93
78
// is re-rendered, the selectors are called again, and
@@ -96,7 +81,9 @@ export function useSelector(selector) {
96
81
latestSubscriptionCallbackError . current = err
97
82
}
98
83
99
- forceRender ( { } )
84
+ const newSubscriptionResult = new Array ( 1 )
85
+ newSubscriptionResult [ 0 ] = latestResult . current
86
+ setSubscriptionResult ( newSubscriptionResult )
100
87
}
101
88
102
89
subscription . onStateChange = checkForUpdates
@@ -107,5 +94,41 @@ export function useSelector(selector) {
107
94
return ( ) => subscription . tryUnsubscribe ( )
108
95
} , [ store , subscription ] )
109
96
110
- return selectedState
97
+ try {
98
+ // If the selector has changed, then it has to be re-evaluated
99
+ if ( selector !== latestSelector . current ) {
100
+ return ( result = selector ( store . getState ( ) ) )
101
+ }
102
+
103
+ // If the subscriptionResult is different, that means that this
104
+ // update has been triggered from the subscription
105
+ if ( subscriptionResult != latestSubscriptionResult . current ) {
106
+ // Before we return the result that was calculated during
107
+ // the subscription we need to check whether an error
108
+ // ocurred during the computation. If that is the case,
109
+ // then we re-evaluate the selector with the latest state
110
+ if ( latestSubscriptionCallbackError . current ) {
111
+ return ( result = selector ( store . getState ( ) ) )
112
+ }
113
+
114
+ // At this point we know for sure that it is safe to return
115
+ // the result that was computed inside the subscription
116
+ return ( result = subscriptionResult [ 0 ] )
117
+ }
118
+
119
+ // We just prevented an unnecessary re-evaluation of the selector!
120
+ return result
121
+ } catch ( err ) {
122
+ let errorMessage = `An error occured while selecting the store state: ${
123
+ err . message
124
+ } .`
125
+
126
+ if ( latestSubscriptionCallbackError . current ) {
127
+ errorMessage += `\nThe error may be correlated with this previous error:\n${
128
+ latestSubscriptionCallbackError . current . stack
129
+ } \n\nOriginal stack trace:`
130
+ }
131
+
132
+ throw new Error ( errorMessage )
133
+ }
111
134
}
0 commit comments