diff --git a/src/components/connectAdvanced.js b/src/components/connectAdvanced.js index 6736e7a9d..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() } @@ -159,7 +163,8 @@ export default function connectAdvanced( } initSelector() { - const { dispatch, getState } = this.store + const { dispatch } = 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/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 = [] 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)