diff --git a/src/components/createConnect.js b/src/components/createConnect.js index ab0df36d7..94d073ae6 100644 --- a/src/components/createConnect.js +++ b/src/components/createConnect.js @@ -34,8 +34,8 @@ export default function createConnect(React) { // Helps track hot reloading. const version = nextVersion++; - function computeStateProps(context) { - const state = context.store.getState(); + function computeStateProps(store) { + const state = store.getState(); const stateProps = finalMapStateToProps(state); invariant( isPlainObject(stateProps), @@ -45,8 +45,8 @@ export default function createConnect(React) { return stateProps; } - function computeDispatchProps(context) { - const { dispatch } = context.store; + function computeDispatchProps(store) { + const { dispatch } = store; const dispatchProps = finalMapDispatchToProps(dispatch); invariant( isPlainObject(dispatchProps), @@ -72,7 +72,11 @@ export default function createConnect(React) { static WrappedComponent = WrappedComponent; static contextTypes = { - store: storeShape.isRequired + store: storeShape + }; + + static propTypes = { + store: storeShape }; shouldComponentUpdate(nextProps, nextState) { @@ -82,13 +86,22 @@ export default function createConnect(React) { constructor(props, context) { super(props, context); this.version = version; - this.stateProps = computeStateProps(context); - this.dispatchProps = computeDispatchProps(context); + this.store = props.store || context.store; + + invariant(this.store, + `Could not find "store" in either the context or ` + + `props of "${this.constructor.displayName}". ` + + `Either wrap the root component in a , ` + + `or explicitly pass "store" as a prop to "${this.constructor.displayName}".` + ); + + this.stateProps = computeStateProps(this.store); + this.dispatchProps = computeDispatchProps(this.store); this.state = this.computeNextState(); } recomputeStateProps() { - const nextStateProps = computeStateProps(this.context); + const nextStateProps = computeStateProps(this.store); if (shallowEqual(nextStateProps, this.stateProps)) { return false; } @@ -98,7 +111,7 @@ export default function createConnect(React) { } recomputeDispatchProps() { - const nextDispatchProps = computeDispatchProps(this.context); + const nextDispatchProps = computeDispatchProps(this.store); if (shallowEqual(nextDispatchProps, this.dispatchProps)) { return false; } @@ -128,7 +141,7 @@ export default function createConnect(React) { trySubscribe() { if (shouldSubscribe && !this.unsubscribe) { - this.unsubscribe = this.context.store.subscribe(::this.handleChange); + this.unsubscribe = this.store.subscribe(::this.handleChange); this.handleChange(); } } diff --git a/test/components/connect.spec.js b/test/components/connect.spec.js index 9fc1da085..0bb936767 100644 --- a/test/components/connect.spec.js +++ b/test/components/connect.spec.js @@ -713,6 +713,52 @@ describe('React', () => { expect(decorated.WrappedComponent).toBe(Container); }); + it('should use the store from the props instead of from the context if present', () => { + class Container extends Component { + render() { + return
; + } + } + + let actualState; + + const expectedState = { foos: {} }; + const decorator = connect(state => { + actualState = state; + return {}; + }); + const Decorated = decorator(Container); + const mockStore = { + dispatch: () => {}, + subscribe: () => {}, + getState: () => expectedState + }; + + TestUtils.renderIntoDocument(); + + expect(actualState).toEqual(expectedState); + }); + + it('should throw an error if the store is not in the props or context', () => { + class Container extends Component { + render() { + return
; + } + } + + const decorator = connect(() => {}); + const Decorated = decorator(Container); + const expectedError = + `Invariant Violation: Could not find "store" in either the context ` + + `or props of "Connect(Container)". Either wrap the root component in a ` + + `, or explicitly pass "store" as a prop to "Connect(Container)".`; + + expect(() => TestUtils.renderIntoDocument()).toThrow(e => { + expect(e.message).toEqual(expectedError); + return true; + }); + }); + it('should return the instance of the wrapped component for use in calling child methods', () => { const store = createStore(() => ({}));