From fbb89ae4e10bdbe585eaf8744a9bf4a0cf48efb4 Mon Sep 17 00:00:00 2001 From: Vinay Hiremath Date: Thu, 18 Aug 2016 01:47:48 -0700 Subject: [PATCH 1/2] bind proper store context in connectAdvanced and the Subscription util --- src/components/connectAdvanced.js | 3 ++- src/utils/Subscription.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/connectAdvanced.js b/src/components/connectAdvanced.js index 6736e7a9d..cafed1658 100644 --- a/src/components/connectAdvanced.js +++ b/src/components/connectAdvanced.js @@ -159,7 +159,8 @@ export default function connectAdvanced( } initSelector() { - const { dispatch, getState } = this.store + const { dispatch } = this.store + let getState = this.store.getState.bind(this.store) const sourceSelector = selectorFactory(dispatch, selectorFactoryOptions) // wrap the selector in an object that tracks its results between runs diff --git a/src/utils/Subscription.js b/src/utils/Subscription.js index b26615ed2..b3b63ec4d 100644 --- a/src/utils/Subscription.js +++ b/src/utils/Subscription.js @@ -5,7 +5,7 @@ export default class Subscription { constructor(store, parentSub) { this.subscribe = parentSub ? parentSub.addNestedSub.bind(parentSub) - : store.subscribe + : store.subscribe.bind(store) this.unsubscribe = null this.nextListeners = this.currentListeners = [] From 6cb82a52ee6751f36ef1c2759ca083161ad3f2ff Mon Sep 17 00:00:00 2001 From: Vinay Hiremath Date: Thu, 18 Aug 2016 15:55:26 -0700 Subject: [PATCH 2/2] add store context-preservation tests --- src/components/connectAdvanced.js | 6 +++- test/components/connect.spec.js | 48 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/components/connectAdvanced.js b/src/components/connectAdvanced.js index cafed1658..7ad013f23 100644 --- a/src/components/connectAdvanced.js +++ b/src/components/connectAdvanced.js @@ -106,6 +106,10 @@ export default function connectAdvanced( `or explicitly pass "${storeKey}" as a prop to "${displayName}".` ) + // make sure `getState` is properly bound in order to avoid breaking + // custom store implementations that rely on the store's context + this.getState = this.store.getState.bind(this.store); + this.initSelector() this.initSubscription() } @@ -160,7 +164,7 @@ export default function connectAdvanced( initSelector() { const { dispatch } = this.store - let getState = this.store.getState.bind(this.store) + const { getState } = this; const sourceSelector = selectorFactory(dispatch, selectorFactoryOptions) // wrap the selector in an object that tracks its results between runs diff --git a/test/components/connect.spec.js b/test/components/connect.spec.js index eadd2e927..b7f8e2a16 100644 --- a/test/components/connect.spec.js +++ b/test/components/connect.spec.js @@ -25,6 +25,30 @@ describe('React', () => { } } + class ContextBoundStore { + constructor(reducer) { + this.reducer = reducer + this.listeners = [] + this.state = undefined + this.dispatch({}) + } + + getState() { + return this.state + } + + subscribe(listener) { + this.listeners.push(listener) + return (() => this.listeners.filter(l => l !== listener)) + } + + dispatch(action) { + this.state = this.reducer(this.getState(), action) + this.listeners.forEach(l => l()) + return action + } + } + ProviderMock.childContextTypes = { store: PropTypes.object.isRequired } @@ -134,6 +158,30 @@ describe('React', () => { expect(stub.props.string).toBe('ab') }) + it('should retain the store\'s context', () => { + const store = new ContextBoundStore(stringBuilder) + + let Container = connect( + state => ({ string: state }) + )(function Container(props) { + return + }) + + const spy = expect.spyOn(console, 'error') + const tree = TestUtils.renderIntoDocument( + + + + ) + spy.destroy() + expect(spy.calls.length).toBe(0) + + const stub = TestUtils.findRenderedComponentWithType(tree, Passthrough) + expect(stub.props.string).toBe('') + store.dispatch({ type: 'APPEND', body: 'a' }) + expect(stub.props.string).toBe('a') + }) + it('should handle dispatches before componentDidMount', () => { const store = createStore(stringBuilder)