Skip to content

Commit 96c53ac

Browse files
authored
Merge pull request #5 from reduxjs/master
merge in master
2 parents 3349bd0 + 47facdb commit 96c53ac

18 files changed

+1373
-2717
lines changed

package-lock.json

Lines changed: 223 additions & 392 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"build:umd:min": "cross-env BABEL_ENV=rollup NODE_ENV=production rollup -c -o dist/react-redux.min.js",
3434
"build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min",
3535
"clean": "rimraf lib dist es coverage",
36-
"lint": "eslint src test/utils test/components test/getTestDeps.js",
36+
"lint": "eslint src test/utils test/components",
3737
"prepare": "npm run clean && npm run build",
3838
"test": "node ./test/run-tests.js",
3939
"coverage": "codecov"
@@ -81,18 +81,17 @@
8181
"create-react-class": "^15.6.3",
8282
"cross-env": "^5.2.0",
8383
"cross-spawn": "^6.0.5",
84-
"enzyme": "^3.3.0",
85-
"enzyme-adapter-react-16": "^1.1.1",
8684
"es3ify": "^0.2.0",
8785
"eslint": "^4.19.1",
8886
"eslint-plugin-import": "^2.12.0",
8987
"eslint-plugin-react": "^7.9.1",
9088
"glob": "^7.1.1",
9189
"jest": "^23.4.1",
90+
"jest-dom": "^1.12.0",
9291
"npm-run": "^5.0.1",
93-
"react": "^16.3.2",
94-
"react-dom": "^16.3.2",
95-
"react-test-renderer": "^16.3.2",
92+
"react": "^16.4.2",
93+
"react-dom": "^16.4.2",
94+
"react-testing-library": "^5.0.0",
9695
"redux": "^4.0.0",
9796
"rimraf": "^2.6.2",
9897
"rollup": "^0.61.1",

src/components/Provider.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export function createProvider(storeKey = 'store') {
3838
}
3939

4040
if (process.env.NODE_ENV !== 'production') {
41-
Provider.prototype.componentDidUpdate = function () {
42-
if (this[storeKey] !== this.props.store) {
41+
Provider.prototype.componentWillReceiveProps = function (nextProps) {
42+
if (this[storeKey] !== nextProps.store) {
4343
warnAboutReceivingStore()
4444
}
4545
}

src/components/connectAdvanced.js

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
11
import hoistStatics from 'hoist-non-react-statics'
22
import invariant from 'invariant'
33
import { Component, createElement } from 'react'
4-
import { polyfill } from 'react-lifecycles-compat'
54

65
import Subscription from '../utils/Subscription'
76
import { storeShape, subscriptionShape } from '../utils/PropTypes'
87

98
let hotReloadingVersion = 0
9+
const dummyState = {}
1010
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,
11+
function makeSelectorStateful(sourceSelector, store) {
12+
// wrap the selector in an object that tracks its results between runs.
13+
const selector = {
14+
run: function runComponentSelector(props) {
15+
try {
16+
const nextProps = sourceSelector(store.getState(), props)
17+
if (nextProps !== selector.props || selector.error) {
18+
selector.shouldComponentUpdate = true
19+
selector.props = nextProps
20+
selector.error = null
2021
}
21-
}
22-
return {
23-
shouldComponentUpdate: false,
24-
}
25-
} catch (error) {
26-
return {
27-
shouldComponentUpdate: true,
28-
error,
22+
} catch (error) {
23+
selector.shouldComponentUpdate = true
24+
selector.error = error
2925
}
3026
}
3127
}
28+
29+
return selector
3230
}
3331

3432
export default function connectAdvanced(
@@ -88,10 +86,6 @@ export default function connectAdvanced(
8886
[subscriptionKey]: subscriptionShape,
8987
}
9088

91-
function getDerivedStateFromProps(nextProps, prevState) {
92-
return prevState.updater(nextProps, prevState)
93-
}
94-
9589
return function wrapWithConnect(WrappedComponent) {
9690
invariant(
9791
typeof WrappedComponent == 'function',
@@ -118,11 +112,15 @@ export default function connectAdvanced(
118112
WrappedComponent
119113
}
120114

115+
// TODO Actually fix our use of componentWillReceiveProps
116+
/* eslint-disable react/no-deprecated */
117+
121118
class Connect extends Component {
122119
constructor(props, context) {
123120
super(props, context)
124121

125122
this.version = version
123+
this.state = {}
126124
this.renderCount = 0
127125
this.store = props[storeKey] || context[storeKey]
128126
this.propsMode = Boolean(props[storeKey])
@@ -134,9 +132,7 @@ export default function connectAdvanced(
134132
`or explicitly pass "${storeKey}" as a prop to "${displayName}".`
135133
)
136134

137-
this.state = {
138-
updater: this.createUpdater()
139-
}
135+
this.initSelector()
140136
this.initSubscription()
141137
}
142138

@@ -159,19 +155,25 @@ export default function connectAdvanced(
159155
// dispatching an action in its componentWillMount, we have to re-run the select and maybe
160156
// re-render.
161157
this.subscription.trySubscribe()
162-
this.runUpdater()
158+
this.selector.run(this.props)
159+
if (this.selector.shouldComponentUpdate) this.forceUpdate()
160+
}
161+
162+
componentWillReceiveProps(nextProps) {
163+
this.selector.run(nextProps)
163164
}
164165

165-
shouldComponentUpdate(_, nextState) {
166-
return nextState.shouldComponentUpdate
166+
shouldComponentUpdate() {
167+
return this.selector.shouldComponentUpdate
167168
}
168169

169170
componentWillUnmount() {
170171
if (this.subscription) this.subscription.tryUnsubscribe()
171172
this.subscription = null
172173
this.notifyNestedSubs = noop
173174
this.store = null
174-
this.isUnmounted = true
175+
this.selector.run = noop
176+
this.selector.shouldComponentUpdate = false
175177
}
176178

177179
getWrappedInstance() {
@@ -186,17 +188,10 @@ export default function connectAdvanced(
186188
this.wrappedInstance = ref
187189
}
188190

189-
createUpdater() {
191+
initSelector() {
190192
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
191-
return makeUpdater(sourceSelector, this.store)
192-
}
193-
194-
runUpdater(callback = noop) {
195-
if (this.isUnmounted) {
196-
return
197-
}
198-
199-
this.setState(prevState => prevState.updater(this.props, prevState), callback)
193+
this.selector = makeSelectorStateful(sourceSelector, this.store)
194+
this.selector.run(this.props)
200195
}
201196

202197
initSubscription() {
@@ -217,7 +212,24 @@ export default function connectAdvanced(
217212
}
218213

219214
onStateChange() {
220-
this.runUpdater(this.notifyNestedSubs)
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()
221233
}
222234

223235
isSubscribed() {
@@ -238,26 +250,31 @@ export default function connectAdvanced(
238250
}
239251

240252
render() {
241-
if (this.state.error) {
242-
throw this.state.error
253+
const selector = this.selector
254+
selector.shouldComponentUpdate = false
255+
256+
if (selector.error) {
257+
throw selector.error
243258
} else {
244-
return createElement(WrappedComponent, this.addExtraProps(this.state.props))
259+
return createElement(WrappedComponent, this.addExtraProps(selector.props))
245260
}
246261
}
247262
}
248263

264+
/* eslint-enable react/no-deprecated */
265+
249266
Connect.WrappedComponent = WrappedComponent
250267
Connect.displayName = displayName
251268
Connect.childContextTypes = childContextTypes
252269
Connect.contextTypes = contextTypes
253270
Connect.propTypes = contextTypes
254-
Connect.getDerivedStateFromProps = getDerivedStateFromProps
255271

256272
if (process.env.NODE_ENV !== 'production') {
257-
Connect.prototype.componentDidUpdate = function componentDidUpdate() {
273+
Connect.prototype.componentWillUpdate = function componentWillUpdate() {
258274
// We are hot reloading!
259275
if (this.version !== version) {
260276
this.version = version
277+
this.initSelector()
261278

262279
// If any connected descendants don't hot reload (and resubscribe in the process), their
263280
// listeners will be lost when we unsubscribe. Unfortunately, by copying over all
@@ -275,16 +292,10 @@ export default function connectAdvanced(
275292
this.subscription.trySubscribe()
276293
oldListeners.forEach(listener => this.subscription.listeners.subscribe(listener))
277294
}
278-
279-
const updater = this.createUpdater()
280-
this.setState({updater})
281-
this.runUpdater()
282295
}
283296
}
284297
}
285298

286-
polyfill(Connect)
287-
288299
return hoistStatics(Connect, WrappedComponent)
289300
}
290301
}

0 commit comments

Comments
 (0)