Skip to content

Commit 31fc951

Browse files
committed
Split Connect into two wrapper components
1 parent 4e654e2 commit 31fc951

File tree

2 files changed

+98
-16
lines changed

2 files changed

+98
-16
lines changed

src/components/Provider.js

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function warnAboutReceivingStore() {
2121
)
2222
}
2323

24-
export function createProvider(storeKey = 'store', subKey) {
24+
export function createProvider(storeKey = 'store') {
2525

2626
class Provider extends Component {
2727

@@ -32,17 +32,35 @@ export function createProvider(storeKey = 'store', subKey) {
3232

3333
this.state = {
3434
storeState : store.getState(),
35-
dispatch : store.dispatch,
35+
store,
3636
};
3737
}
3838

3939
componentDidMount() {
40-
const {store} = this.props;
40+
this.subscribe();
41+
}
42+
43+
subscribe() {
44+
const {store} = this.props;
45+
46+
this.unsubscribe = store.subscribe( () => {
47+
const newStoreState = store.getState();
48+
49+
this.setState(providerState => {
50+
// If the value is the same, skip the unnecessary state update.
51+
if(providerState.storeState === newStoreState) {
52+
return null;
53+
}
54+
55+
return {storeState : newStoreState};
56+
})
57+
});
4158

42-
// TODO What about any actions that might have been dispatched between ctor and cDM?
43-
this.unsubscribe = store.subscribe( () => {
44-
this.setState({storeState : store.getState()});
45-
});
59+
// Actions might have been dispatched between render and mount - handle those
60+
const postMountStoreState = store.getState();
61+
if(postMountStoreState !== this.state.storeState) {
62+
this.setState({storeState : postMountStoreState});
63+
}
4664
}
4765

4866
render() {
@@ -55,8 +73,8 @@ export function createProvider(storeKey = 'store', subKey) {
5573
}
5674

5775
if (process.env.NODE_ENV !== 'production') {
58-
Provider.prototype.componentWillReceiveProps = function (nextProps) {
59-
if (this[storeKey] !== nextProps.store) {
76+
Provider.getDerivedStateFromProps = function (props, state) {
77+
if (state.store !== props.store) {
6078
warnAboutReceivingStore()
6179
}
6280
}

src/components/connectAdvanced.js

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import invariant from 'invariant'
33
import React, { Component, createElement } from 'react'
44

55
import {ReactReduxContext} from "./context";
6-
import { storeShape } from '../utils/PropTypes'
6+
import shallowEqual from '../utils/shallowEqual'
77

88
let hotReloadingVersion = 0
99
const dummyState = {}
@@ -104,6 +104,67 @@ export default function connectAdvanced(
104104
WrappedComponent
105105
}
106106

107+
108+
class ConnectInner extends Component {
109+
constructor(props) {
110+
super(props);
111+
112+
this.state = {
113+
storeState : props.storeState,
114+
wrapperProps : props.wrapperProps,
115+
renderCount : 0,
116+
store : props.store,
117+
error : null,
118+
childPropsSelector : this.createChildSelector(props.store),
119+
childProps : {},
120+
}
121+
122+
this.state = {
123+
...this.state,
124+
...ConnectInner.getChildPropsState(props, this.state)
125+
}
126+
}
127+
128+
createChildSelector(store = this.state.store) {
129+
return selectorFactory(store.dispatch, selectorFactoryOptions)
130+
}
131+
132+
static getChildPropsState(props, state) {
133+
try {
134+
const nextProps = state.childPropsSelector(state.storeState, props.wrapperProps)
135+
if (nextProps === state.childProps) return null
136+
return { childProps: nextProps }
137+
} catch (error) {
138+
return { error }
139+
}
140+
}
141+
142+
static getDerivedStateFromProps(props, state) {
143+
if ((connectOptions.pure && shallowEqual(props.wrapperProps, state.wrapperProps)) || state.error){
144+
return null;
145+
}
146+
147+
const nextChildProps = ConnectInner.getChildPropsState(props, state)
148+
149+
return {
150+
...nextChildProps,
151+
wrapperProps : props.wrapperProps,
152+
}
153+
}
154+
155+
shouldComponentUpdate(nextProps, nextState) {
156+
return nextState.childProps !== this.state.childProps;
157+
}
158+
159+
render() {
160+
if(this.state.error) {
161+
throw this.state.error;
162+
}
163+
164+
return <WrappedComponent {...this.state.childProps} />
165+
}
166+
}
167+
107168
class Connect extends Component {
108169
constructor(props) {
109170
super(props)
@@ -113,8 +174,8 @@ export default function connectAdvanced(
113174
this.storeState = null;
114175

115176

116-
this.setWrappedInstance = this.setWrappedInstance.bind(this)
117-
this.renderChild = this.renderChild.bind(this);
177+
//this.setWrappedInstance = this.setWrappedInstance.bind(this)
178+
this.renderInner = this.renderInner.bind(this);
118179

119180
// TODO How do we express the invariant of needing a Provider when it's used in render()?
120181
/*
@@ -181,15 +242,16 @@ export default function connectAdvanced(
181242
// instance. a singleton memoized selector would then be holding a reference to the
182243
// instance, preventing the instance from being garbage collected, and that would be bad
183244
const withExtras = { ...props }
184-
if (withRef) withExtras.ref = this.setWrappedInstance
245+
//if (withRef) withExtras.ref = this.setWrappedInstance
185246
if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
186247

187248
return withExtras
188249
}
189250

190-
renderChild(providerValue) {
191-
const {storeState, dispatch} = providerValue;
251+
renderInner(providerValue) {
252+
const {storeState, store} = providerValue;
192253

254+
/*
193255
this.storeState = storeState;
194256
195257
if(this.selector) {
@@ -214,12 +276,14 @@ export default function connectAdvanced(
214276
}
215277
216278
return this.renderedElement;
279+
*/
280+
return <ConnectInner storeState={storeState} store={store} wrapperProps={this.props} />
217281
}
218282

219283
render() {
220284
return (
221285
<ReactReduxContext.Consumer>
222-
{this.renderChild}
286+
{this.renderInner}
223287
</ReactReduxContext.Consumer>
224288
)
225289
}

0 commit comments

Comments
 (0)