Skip to content

Commit 21f0b43

Browse files
author
Andrew McCloud
committed
Use transactional setState for handling store changes to fix reduxjs#368
1 parent 253ce8b commit 21f0b43

File tree

2 files changed

+47
-130
lines changed

2 files changed

+47
-130
lines changed

src/components/connect.js

+44-127
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,6 @@ function getDisplayName(WrappedComponent) {
1919
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
2020
}
2121

22-
let errorObject = { value: null }
23-
function tryCatch(fn, ctx) {
24-
try {
25-
return fn.apply(ctx)
26-
} catch (e) {
27-
errorObject.value = e
28-
return errorObject
29-
}
30-
}
31-
3222
// Helps track hot reloading.
3323
let nextVersion = 0
3424

@@ -47,7 +37,6 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
4737

4838
const finalMergeProps = mergeProps || defaultMergeProps
4939
const { pure = true, withRef = false } = options
50-
const checkMergedEquals = pure && finalMergeProps !== defaultMergeProps
5140

5241
// Helps track hot reloading.
5342
const version = nextVersion++
@@ -73,8 +62,8 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
7362
}
7463

7564
class Connect extends Component {
76-
shouldComponentUpdate() {
77-
return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged
65+
shouldComponentUpdate(nextProps, nextState) {
66+
return !pure || !shallowEqual(nextState.mergedProps, this.state.mergedProps)
7867
}
7968

8069
constructor(props, context) {
@@ -89,9 +78,12 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
8978
`or explicitly pass "store" as a prop to "${connectDisplayName}".`
9079
)
9180

92-
const storeState = this.store.getState()
93-
this.state = { storeState }
94-
this.clearCache()
81+
this.storeState = this.store.getState()
82+
this.stateProps = this.computeStateProps(this.store, this.props)
83+
this.dispatchProps = this.computeDispatchProps(this.store, this.props)
84+
this.state = {
85+
mergedProps: computeMergedProps(this.stateProps, this.dispatchProps, this.props)
86+
}
9587
}
9688

9789
computeStateProps(store, props) {
@@ -160,36 +152,6 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
160152
return mappedDispatch
161153
}
162154

163-
updateStatePropsIfNeeded() {
164-
const nextStateProps = this.computeStateProps(this.store, this.props)
165-
if (this.stateProps && shallowEqual(nextStateProps, this.stateProps)) {
166-
return false
167-
}
168-
169-
this.stateProps = nextStateProps
170-
return true
171-
}
172-
173-
updateDispatchPropsIfNeeded() {
174-
const nextDispatchProps = this.computeDispatchProps(this.store, this.props)
175-
if (this.dispatchProps && shallowEqual(nextDispatchProps, this.dispatchProps)) {
176-
return false
177-
}
178-
179-
this.dispatchProps = nextDispatchProps
180-
return true
181-
}
182-
183-
updateMergedPropsIfNeeded() {
184-
const nextMergedProps = computeMergedProps(this.stateProps, this.dispatchProps, this.props)
185-
if (this.mergedProps && checkMergedEquals && shallowEqual(nextMergedProps, this.mergedProps)) {
186-
return false
187-
}
188-
189-
this.mergedProps = nextMergedProps
190-
return true
191-
}
192-
193155
isSubscribed() {
194156
return typeof this.unsubscribe === 'function'
195157
}
@@ -213,9 +175,17 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
213175
}
214176

215177
componentWillReceiveProps(nextProps) {
216-
if (!pure || !shallowEqual(nextProps, this.props)) {
217-
this.haveOwnPropsChanged = true
178+
if (this.doStatePropsDependOnOwnProps) {
179+
this.stateProps = this.computeStateProps(this.store, nextProps)
218180
}
181+
182+
if (this.doDispatchPropsDependOnOwnProps) {
183+
this.dispatchProps = this.computeDispatchProps(this.store, nextProps)
184+
}
185+
186+
this.setState({
187+
mergedProps: computeMergedProps(this.stateProps, this.dispatchProps, nextProps)
188+
})
219189
}
220190

221191
componentWillUnmount() {
@@ -224,14 +194,9 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
224194
}
225195

226196
clearCache() {
197+
this.storeState = null
227198
this.dispatchProps = null
228199
this.stateProps = null
229-
this.mergedProps = null
230-
this.haveOwnPropsChanged = true
231-
this.hasStoreStateChanged = true
232-
this.haveStatePropsBeenPrecalculated = false
233-
this.statePropsPrecalculationError = null
234-
this.renderedElement = null
235200
this.finalMapDispatchToProps = null
236201
this.finalMapStateToProps = null
237202
}
@@ -241,25 +206,32 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
241206
return
242207
}
243208

244-
const storeState = this.store.getState()
245-
const prevStoreState = this.state.storeState
246-
if (pure && prevStoreState === storeState) {
247-
return
248-
}
209+
this.setState((previousState, currentProps) => {
210+
let nextStoreState = this.store.getState()
249211

250-
if (pure && !this.doStatePropsDependOnOwnProps) {
251-
const haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded, this)
252-
if (!haveStatePropsChanged) {
212+
if (nextStoreState === this.storeState) {
253213
return
254214
}
255-
if (haveStatePropsChanged === errorObject) {
256-
this.statePropsPrecalculationError = errorObject.value
215+
216+
this.storeState = nextStoreState
217+
218+
let nextStateProps = this.computeStateProps(this.store, currentProps)
219+
let nextDispatchProps = this.computeDispatchProps(this.store, currentProps)
220+
221+
let statePropsChanged = this.stateProps && !shallowEqual(nextStateProps, this.stateProps)
222+
let dispatchPropsChanged = this.dispatchProps && !shallowEqual(nextDispatchProps, this.dispatchProps)
223+
224+
if (!statePropsChanged && !dispatchPropsChanged) {
225+
return
257226
}
258-
this.haveStatePropsBeenPrecalculated = true
259-
}
260227

261-
this.hasStoreStateChanged = true
262-
this.setState({ storeState })
228+
this.stateProps = nextStateProps
229+
this.dispatchProps = nextDispatchProps
230+
231+
return {
232+
mergedProps: computeMergedProps(this.stateProps, this.dispatchProps, currentProps)
233+
}
234+
})
263235
}
264236

265237
getWrappedInstance() {
@@ -272,71 +244,16 @@ export default function connect(mapStateToProps, mapDispatchToProps, mergeProps,
272244
}
273245

274246
render() {
275-
const {
276-
haveOwnPropsChanged,
277-
hasStoreStateChanged,
278-
haveStatePropsBeenPrecalculated,
279-
statePropsPrecalculationError,
280-
renderedElement
281-
} = this
282-
283-
this.haveOwnPropsChanged = false
284-
this.hasStoreStateChanged = false
285-
this.haveStatePropsBeenPrecalculated = false
286-
this.statePropsPrecalculationError = null
287-
288-
if (statePropsPrecalculationError) {
289-
throw statePropsPrecalculationError
290-
}
291-
292-
let shouldUpdateStateProps = true
293-
let shouldUpdateDispatchProps = true
294-
if (pure && renderedElement) {
295-
shouldUpdateStateProps = hasStoreStateChanged || (
296-
haveOwnPropsChanged && this.doStatePropsDependOnOwnProps
297-
)
298-
shouldUpdateDispatchProps =
299-
haveOwnPropsChanged && this.doDispatchPropsDependOnOwnProps
300-
}
301-
302-
let haveStatePropsChanged = false
303-
let haveDispatchPropsChanged = false
304-
if (haveStatePropsBeenPrecalculated) {
305-
haveStatePropsChanged = true
306-
} else if (shouldUpdateStateProps) {
307-
haveStatePropsChanged = this.updateStatePropsIfNeeded()
308-
}
309-
if (shouldUpdateDispatchProps) {
310-
haveDispatchPropsChanged = this.updateDispatchPropsIfNeeded()
311-
}
312-
313-
let haveMergedPropsChanged = true
314-
if (
315-
haveStatePropsChanged ||
316-
haveDispatchPropsChanged ||
317-
haveOwnPropsChanged
318-
) {
319-
haveMergedPropsChanged = this.updateMergedPropsIfNeeded()
320-
} else {
321-
haveMergedPropsChanged = false
322-
}
323-
324-
if (!haveMergedPropsChanged && renderedElement) {
325-
return renderedElement
326-
}
327-
328247
if (withRef) {
329-
this.renderedElement = createElement(WrappedComponent, {
330-
...this.mergedProps,
248+
return createElement(WrappedComponent, {
249+
...this.state.mergedProps,
331250
ref: 'wrappedInstance'
332251
})
333252
} else {
334-
this.renderedElement = createElement(WrappedComponent,
335-
this.mergedProps
253+
return createElement(WrappedComponent,
254+
this.state.mergedProps
336255
)
337256
}
338-
339-
return this.renderedElement
340257
}
341258
}
342259

test/components/connect.spec.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1611,17 +1611,17 @@ describe('React', () => {
16111611
store.dispatch({ type: 'APPEND', body: 'a' })
16121612
expect(mapStateCalls).toBe(2)
16131613
expect(renderCalls).toBe(1)
1614-
expect(spy.calls.length).toBe(0)
1614+
expect(spy.calls.length).toBe(1)
16151615

16161616
store.dispatch({ type: 'APPEND', body: 'a' })
16171617
expect(mapStateCalls).toBe(3)
16181618
expect(renderCalls).toBe(1)
1619-
expect(spy.calls.length).toBe(0)
1619+
expect(spy.calls.length).toBe(2)
16201620

16211621
store.dispatch({ type: 'APPEND', body: 'a' })
16221622
expect(mapStateCalls).toBe(4)
16231623
expect(renderCalls).toBe(2)
1624-
expect(spy.calls.length).toBe(1)
1624+
expect(spy.calls.length).toBe(3)
16251625

16261626
spy.destroy()
16271627
})

0 commit comments

Comments
 (0)