1
1
import hoistStatics from 'hoist-non-react-statics'
2
2
import invariant from 'invariant'
3
3
import { Component , createElement } from 'react'
4
- import { polyfill } from 'react-lifecycles-compat'
5
4
6
5
import Subscription from '../utils/Subscription'
7
6
import { storeShape , subscriptionShape } from '../utils/PropTypes'
8
7
9
8
let hotReloadingVersion = 0
10
9
function noop ( ) { }
11
- function makeUpdater ( sourceSelector , store ) {
12
- return function updater ( props , prevState ) {
13
- try {
14
- const nextProps = sourceSelector ( store . getState ( ) , props )
15
- if ( nextProps !== prevState . props || prevState . error ) {
16
- return {
17
- shouldComponentUpdate : true ,
18
- props : nextProps ,
19
- error : null ,
20
- }
21
- }
22
- return {
23
- shouldComponentUpdate : false ,
24
- }
25
- } catch ( error ) {
26
- return {
27
- shouldComponentUpdate : true ,
28
- error,
29
- }
30
- }
31
- }
32
- }
33
10
34
11
export default function connectAdvanced (
35
12
/*
@@ -88,10 +65,6 @@ export default function connectAdvanced(
88
65
[ subscriptionKey ] : subscriptionShape ,
89
66
}
90
67
91
- function getDerivedStateFromProps ( nextProps , prevState ) {
92
- return prevState . updater ( nextProps , prevState )
93
- }
94
-
95
68
return function wrapWithConnect ( WrappedComponent ) {
96
69
invariant (
97
70
typeof WrappedComponent == 'function' ,
@@ -134,10 +107,14 @@ export default function connectAdvanced(
134
107
`or explicitly pass "${ storeKey } " as a prop to "${ displayName } ".`
135
108
)
136
109
110
+ this . createSelector ( )
137
111
this . state = {
138
- updater : this . createUpdater ( )
112
+ updateCount : 0
139
113
}
114
+ this . storeState = this . store . getState ( )
140
115
this . initSubscription ( )
116
+ this . derivedProps = this . derivedPropsUpdater ( )
117
+ this . received = this . props
141
118
}
142
119
143
120
getChildContext ( ) {
@@ -159,11 +136,17 @@ export default function connectAdvanced(
159
136
// dispatching an action in its componentWillMount, we have to re-run the select and maybe
160
137
// re-render.
161
138
this . subscription . trySubscribe ( )
162
- this . runUpdater ( )
139
+ this . triggerUpdateOnStoreStateChange ( )
163
140
}
164
141
165
- shouldComponentUpdate ( _ , nextState ) {
166
- return nextState . shouldComponentUpdate
142
+ shouldComponentUpdate ( nextProps ) {
143
+ this . received = nextProps
144
+ // received a prop update, store state updates are handled in onStateChange
145
+ const oldProps = this . derivedProps
146
+ const newProps = this . updateDerivedProps ( nextProps )
147
+ if ( this . error ) return true
148
+ const sCU = newProps !== oldProps
149
+ return sCU
167
150
}
168
151
169
152
componentWillUnmount ( ) {
@@ -174,6 +157,31 @@ export default function connectAdvanced(
174
157
this . isUnmounted = true
175
158
}
176
159
160
+ updateDerivedProps ( nextProps ) {
161
+ this . derivedProps = this . derivedPropsUpdater ( nextProps )
162
+ return this . derivedProps
163
+ }
164
+
165
+ derivedPropsUpdater ( props = this . props ) {
166
+ // runs when props change, or the store state changes
167
+ // and generates the derived props for connected components
168
+ try {
169
+ const nextProps = this . sourceSelector ( this . storeState , props )
170
+ if ( nextProps !== this . derivedProps || this . error ) {
171
+ this . error = null
172
+ return nextProps
173
+ }
174
+ return this . derivedProps
175
+ } catch ( error ) {
176
+ this . error = error
177
+ return this . derivedProps
178
+ }
179
+ }
180
+
181
+ createSelector ( ) {
182
+ this . sourceSelector = selectorFactory ( this . store . dispatch , selectorFactoryOptions )
183
+ }
184
+
177
185
getWrappedInstance ( ) {
178
186
invariant ( withRef ,
179
187
`To access the wrapped instance, you need to specify ` +
@@ -186,17 +194,24 @@ export default function connectAdvanced(
186
194
this . wrappedInstance = ref
187
195
}
188
196
189
- createUpdater ( ) {
190
- const sourceSelector = selectorFactory ( this . store . dispatch , selectorFactoryOptions )
191
- return makeUpdater ( sourceSelector , this . store )
192
- }
193
-
194
- runUpdater ( callback = noop ) {
197
+ triggerUpdateOnStoreStateChange ( callback = noop ) {
198
+ // runs when an action is dispatched by the store we are listening to
199
+ // if the store state has changed, we save that and update the component state
200
+ // to force a re-generation of derived props
195
201
if ( this . isUnmounted ) {
196
202
return
197
203
}
198
204
199
- this . setState ( prevState => prevState . updater ( this . props , prevState ) , callback )
205
+ this . setState ( prevState => {
206
+ const newState = this . store . getState ( )
207
+ if ( this . storeState === newState ) {
208
+ return prevState
209
+ }
210
+ this . storeState = newState
211
+ return {
212
+ updateCount : prevState . updateCount ++
213
+ }
214
+ } , callback )
200
215
}
201
216
202
217
initSubscription ( ) {
@@ -217,7 +232,7 @@ export default function connectAdvanced(
217
232
}
218
233
219
234
onStateChange ( ) {
220
- this . runUpdater ( this . notifyNestedSubs )
235
+ this . triggerUpdateOnStoreStateChange ( this . notifyNestedSubs )
221
236
}
222
237
223
238
isSubscribed ( ) {
@@ -238,10 +253,16 @@ export default function connectAdvanced(
238
253
}
239
254
240
255
render ( ) {
241
- if ( this . state . error ) {
242
- throw this . state . error
256
+ if ( this . received !== this . props ) {
257
+ // forceUpdate() was called on this component, which skips sCU
258
+ // so manually update derived props
259
+ this . received = this . props
260
+ this . updateDerivedProps ( this . props )
261
+ }
262
+ if ( this . error ) {
263
+ throw this . error
243
264
} else {
244
- return createElement ( WrappedComponent , this . addExtraProps ( this . state . props ) )
265
+ return createElement ( WrappedComponent , this . addExtraProps ( this . derivedProps ) )
245
266
}
246
267
}
247
268
}
@@ -251,7 +272,6 @@ export default function connectAdvanced(
251
272
Connect . childContextTypes = childContextTypes
252
273
Connect . contextTypes = contextTypes
253
274
Connect . propTypes = contextTypes
254
- Connect . getDerivedStateFromProps = getDerivedStateFromProps
255
275
256
276
if ( process . env . NODE_ENV !== 'production' ) {
257
277
Connect . prototype . componentDidUpdate = function componentDidUpdate ( ) {
@@ -276,15 +296,12 @@ export default function connectAdvanced(
276
296
oldListeners . forEach ( listener => this . subscription . listeners . subscribe ( listener ) )
277
297
}
278
298
279
- const updater = this . createUpdater ( )
280
- this . setState ( { updater} )
281
- this . runUpdater ( )
299
+ this . createSelector ( )
300
+ this . triggerUpdateOnStoreStateChange ( )
282
301
}
283
302
}
284
303
}
285
304
286
- polyfill ( Connect )
287
-
288
305
return hoistStatics ( Connect , WrappedComponent )
289
306
}
290
307
}
0 commit comments