1
1
import hoistStatics from 'hoist-non-react-statics'
2
2
import invariant from 'invariant'
3
- import { Component , createElement } from 'react'
3
+ import React , { Component , createElement } from 'react'
4
4
5
- import Subscription from '../utils/Subscription'
6
- import { storeShape , subscriptionShape } from '../utils/PropTypes'
5
+ import { ReactReduxContext } from "./context" ;
6
+ import { storeShape } from '../utils/PropTypes'
7
7
8
8
let hotReloadingVersion = 0
9
9
const dummyState = { }
10
10
function noop ( ) { }
11
- function makeSelectorStateful ( sourceSelector , store ) {
11
+ function makeSelectorStateful ( sourceSelector ) {
12
12
// wrap the selector in an object that tracks its results between runs.
13
13
const selector = {
14
- run : function runComponentSelector ( props ) {
14
+ run : function runComponentSelector ( props , storeState ) {
15
15
try {
16
- const nextProps = sourceSelector ( store . getState ( ) , props )
16
+ const nextProps = sourceSelector ( storeState , props )
17
17
if ( nextProps !== selector . props || selector . error ) {
18
18
selector . shouldComponentUpdate = true
19
19
selector . props = nextProps
@@ -75,20 +75,12 @@ export default function connectAdvanced(
75
75
...connectOptions
76
76
} = { }
77
77
) {
78
- const subscriptionKey = storeKey + 'Subscription'
79
78
const version = hotReloadingVersion ++
80
79
81
- const contextTypes = {
82
- [ storeKey ] : storeShape ,
83
- [ subscriptionKey ] : subscriptionShape ,
84
- }
85
- const childContextTypes = {
86
- [ subscriptionKey ] : subscriptionShape ,
87
- }
88
80
89
81
return function wrapWithConnect ( WrappedComponent ) {
90
82
invariant (
91
- typeof WrappedComponent == 'function' ,
83
+ typeof WrappedComponent === 'function' ,
92
84
`You must pass a component to the function returned by ` +
93
85
`${ methodName } . Instead received ${ JSON . stringify ( WrappedComponent ) } `
94
86
)
@@ -112,37 +104,26 @@ export default function connectAdvanced(
112
104
WrappedComponent
113
105
}
114
106
115
- // TODO Actually fix our use of componentWillReceiveProps
116
- /* eslint-disable react/no-deprecated */
117
-
118
107
class Connect extends Component {
119
- constructor ( props , context ) {
120
- super ( props , context )
108
+ constructor ( props ) {
109
+ super ( props )
121
110
122
111
this . version = version
123
- this . state = { }
124
112
this . renderCount = 0
125
- this . store = props [ storeKey ] || context [ storeKey ]
126
- this . propsMode = Boolean ( props [ storeKey ] )
113
+ this . storeState = null ;
114
+
115
+
127
116
this . setWrappedInstance = this . setWrappedInstance . bind ( this )
117
+ this . renderChild = this . renderChild . bind ( this ) ;
128
118
119
+ // TODO How do we express the invariant of needing a Provider when it's used in render()?
120
+ /*
129
121
invariant(this.store,
130
122
`Could not find "${storeKey}" in either the context or props of ` +
131
123
`"${displayName}". Either wrap the root component in a <Provider>, ` +
132
124
`or explicitly pass "${storeKey}" as a prop to "${displayName}".`
133
125
)
134
-
135
- this . initSelector ( )
136
- this . initSubscription ( )
137
- }
138
-
139
- getChildContext ( ) {
140
- // If this component received store from props, its subscription should be transparent
141
- // to any descendants receiving store+subscription from context; it passes along
142
- // subscription passed to it. Otherwise, it shadows the parent subscription, which allows
143
- // Connect to control ordering of notifications to flow top-down.
144
- const subscription = this . propsMode ? null : this . subscription
145
- return { [ subscriptionKey ] : subscription || this . context [ subscriptionKey ] }
126
+ */
146
127
}
147
128
148
129
componentDidMount ( ) {
@@ -154,24 +135,22 @@ export default function connectAdvanced(
154
135
// To handle the case where a child component may have triggered a state change by
155
136
// dispatching an action in its componentWillMount, we have to re-run the select and maybe
156
137
// re-render.
157
- this . subscription . trySubscribe ( )
158
- this . selector . run ( this . props )
138
+ this . selector . run ( this . props , this . storeState ) ;
159
139
if ( this . selector . shouldComponentUpdate ) this . forceUpdate ( )
160
140
}
161
141
162
- componentWillReceiveProps ( nextProps ) {
163
- this . selector . run ( nextProps )
142
+
143
+ UNSAFE_componentWillReceiveProps ( nextProps ) {
144
+ // TODO Do we still want/need to implement sCU / cWRP now?
145
+ this . selector . run ( nextProps , this . storeState ) ;
164
146
}
165
147
166
148
shouldComponentUpdate ( ) {
167
149
return this . selector . shouldComponentUpdate
168
150
}
169
151
152
+
170
153
componentWillUnmount ( ) {
171
- if ( this . subscription ) this . subscription . tryUnsubscribe ( )
172
- this . subscription = null
173
- this . notifyNestedSubs = noop
174
- this . store = null
175
154
this . selector . run = noop
176
155
this . selector . shouldComponentUpdate = false
177
156
}
@@ -188,87 +167,71 @@ export default function connectAdvanced(
188
167
this . wrappedInstance = ref
189
168
}
190
169
191
- initSelector ( ) {
192
- const sourceSelector = selectorFactory ( this . store . dispatch , selectorFactoryOptions )
193
- this . selector = makeSelectorStateful ( sourceSelector , this . store )
194
- this . selector . run ( this . props )
195
- }
196
-
197
- initSubscription ( ) {
198
- if ( ! shouldHandleStateChanges ) return
199
-
200
- // parentSub's source should match where store came from: props vs. context. A component
201
- // connected to the store via props shouldn't use subscription from context, or vice versa.
202
- const parentSub = ( this . propsMode ? this . props : this . context ) [ subscriptionKey ]
203
- this . subscription = new Subscription ( this . store , parentSub , this . onStateChange . bind ( this ) )
204
-
205
- // `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in
206
- // the middle of the notification loop, where `this.subscription` will then be null. An
207
- // extra null check every change can be avoided by copying the method onto `this` and then
208
- // replacing it with a no-op on unmount. This can probably be avoided if Subscription's
209
- // listeners logic is changed to not call listeners that have been unsubscribed in the
210
- // middle of the notification loop.
211
- this . notifyNestedSubs = this . subscription . notifyNestedSubs . bind ( this . subscription )
212
- }
213
-
214
- onStateChange ( ) {
215
- this . selector . run ( this . props )
216
-
217
- if ( ! this . selector . shouldComponentUpdate ) {
218
- this . notifyNestedSubs ( )
219
- } else {
220
- this . componentDidUpdate = this . notifyNestedSubsOnComponentDidUpdate
221
- this . setState ( dummyState )
222
- }
223
- }
224
-
225
- notifyNestedSubsOnComponentDidUpdate ( ) {
226
- // `componentDidUpdate` is conditionally implemented when `onStateChange` determines it
227
- // needs to notify nested subs. Once called, it unimplements itself until further state
228
- // changes occur. Doing it this way vs having a permanent `componentDidUpdate` that does
229
- // a boolean check every time avoids an extra method call most of the time, resulting
230
- // in some perf boost.
231
- this . componentDidUpdate = undefined
232
- this . notifyNestedSubs ( )
233
- }
234
-
235
- isSubscribed ( ) {
236
- return Boolean ( this . subscription ) && this . subscription . isSubscribed ( )
170
+ initSelector ( dispatch , storeState ) {
171
+ const sourceSelector = selectorFactory ( dispatch , selectorFactoryOptions )
172
+ this . selector = makeSelectorStateful ( sourceSelector )
173
+ this . selector . run ( this . props , storeState ) ;
237
174
}
238
175
239
176
addExtraProps ( props ) {
240
- if ( ! withRef && ! renderCountProp && ! ( this . propsMode && this . subscription ) ) return props
177
+ if ( ! withRef && ! renderCountProp ) return props ;
178
+
241
179
// make a shallow copy so that fields added don't leak to the original selector.
242
180
// this is especially important for 'ref' since that's a reference back to the component
243
181
// instance. a singleton memoized selector would then be holding a reference to the
244
182
// instance, preventing the instance from being garbage collected, and that would be bad
245
183
const withExtras = { ...props }
246
184
if ( withRef ) withExtras . ref = this . setWrappedInstance
247
185
if ( renderCountProp ) withExtras [ renderCountProp ] = this . renderCount ++
248
- if ( this . propsMode && this . subscription ) withExtras [ subscriptionKey ] = this . subscription
186
+
249
187
return withExtras
250
188
}
251
189
252
- render ( ) {
253
- const selector = this . selector
254
- selector . shouldComponentUpdate = false
190
+ renderChild ( providerValue ) {
191
+ const { storeState, dispatch} = providerValue ;
255
192
256
- if ( selector . error ) {
257
- throw selector . error
258
- } else {
259
- return createElement ( WrappedComponent , this . addExtraProps ( selector . props ) )
260
- }
193
+ this . storeState = storeState ;
194
+
195
+ if ( this . selector ) {
196
+ this . selector . run ( this . props , storeState ) ;
197
+ }
198
+ else {
199
+ this . initSelector ( dispatch , storeState ) ;
200
+ }
201
+
202
+ if ( this . selector . error ) {
203
+ // TODO This will unmount the whole tree now that we're throwing in render. Good idea?
204
+ // TODO Related: https://github.com/reactjs/react-redux/issues/802
205
+ throw this . selector . error
206
+ }
207
+ else if ( this . selector . shouldComponentUpdate ) {
208
+ //console.log(`Re-rendering component (${displayName})`, this.selector.props);
209
+ this . selector . shouldComponentUpdate = false ;
210
+ this . renderedElement = createElement ( WrappedComponent , this . addExtraProps ( this . selector . props ) ) ;
211
+ }
212
+ else {
213
+ //console.log(`Returning existing render result (${displayName})`, this.props)
214
+ }
215
+
216
+ return this . renderedElement ;
261
217
}
262
- }
263
218
264
- /* eslint-enable react/no-deprecated */
219
+ render ( ) {
220
+ return (
221
+ < ReactReduxContext . Consumer >
222
+ { this . renderChild }
223
+ </ ReactReduxContext . Consumer >
224
+ )
225
+ }
226
+ }
265
227
266
228
Connect . WrappedComponent = WrappedComponent
267
229
Connect . displayName = displayName
268
- Connect . childContextTypes = childContextTypes
269
- Connect . contextTypes = contextTypes
270
- Connect . propTypes = contextTypes
230
+ // TODO We're losing the ability to add a store as a prop. Not sure there's anything we can do about that.
231
+ //Connect.propTypes = contextTypes
271
232
233
+ // TODO With connect no longer managing subscriptions, I _think_ is is all unneeded
234
+ /*
272
235
if (process.env.NODE_ENV !== 'production') {
273
236
Connect.prototype.componentWillUpdate = function componentWillUpdate() {
274
237
// We are hot reloading!
@@ -295,6 +258,7 @@ export default function connectAdvanced(
295
258
}
296
259
}
297
260
}
261
+ */
298
262
299
263
return hoistStatics ( Connect , WrappedComponent )
300
264
}
0 commit comments