Skip to content

Commit 77a9aa4

Browse files
authored
Merge pull request #465 from vhmth/vinay/proper_store_binding
bind proper store context in connectAdvanced and the Subscription util
2 parents edb93bd + 6cb82a5 commit 77a9aa4

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

src/components/connectAdvanced.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ export default function connectAdvanced(
106106
`or explicitly pass "${storeKey}" as a prop to "${displayName}".`
107107
)
108108

109+
// make sure `getState` is properly bound in order to avoid breaking
110+
// custom store implementations that rely on the store's context
111+
this.getState = this.store.getState.bind(this.store);
112+
109113
this.initSelector()
110114
this.initSubscription()
111115
}
@@ -159,7 +163,8 @@ export default function connectAdvanced(
159163
}
160164

161165
initSelector() {
162-
const { dispatch, getState } = this.store
166+
const { dispatch } = this.store
167+
const { getState } = this;
163168
const sourceSelector = selectorFactory(dispatch, selectorFactoryOptions)
164169

165170
// wrap the selector in an object that tracks its results between runs

src/utils/Subscription.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default class Subscription {
55
constructor(store, parentSub) {
66
this.subscribe = parentSub
77
? parentSub.addNestedSub.bind(parentSub)
8-
: store.subscribe
8+
: store.subscribe.bind(store)
99

1010
this.unsubscribe = null
1111
this.nextListeners = this.currentListeners = []

test/components/connect.spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,30 @@ describe('React', () => {
2525
}
2626
}
2727

28+
class ContextBoundStore {
29+
constructor(reducer) {
30+
this.reducer = reducer
31+
this.listeners = []
32+
this.state = undefined
33+
this.dispatch({})
34+
}
35+
36+
getState() {
37+
return this.state
38+
}
39+
40+
subscribe(listener) {
41+
this.listeners.push(listener)
42+
return (() => this.listeners.filter(l => l !== listener))
43+
}
44+
45+
dispatch(action) {
46+
this.state = this.reducer(this.getState(), action)
47+
this.listeners.forEach(l => l())
48+
return action
49+
}
50+
}
51+
2852
ProviderMock.childContextTypes = {
2953
store: PropTypes.object.isRequired
3054
}
@@ -134,6 +158,30 @@ describe('React', () => {
134158
expect(stub.props.string).toBe('ab')
135159
})
136160

161+
it('should retain the store\'s context', () => {
162+
const store = new ContextBoundStore(stringBuilder)
163+
164+
let Container = connect(
165+
state => ({ string: state })
166+
)(function Container(props) {
167+
return <Passthrough {...props}/>
168+
})
169+
170+
const spy = expect.spyOn(console, 'error')
171+
const tree = TestUtils.renderIntoDocument(
172+
<ProviderMock store={store}>
173+
<Container />
174+
</ProviderMock>
175+
)
176+
spy.destroy()
177+
expect(spy.calls.length).toBe(0)
178+
179+
const stub = TestUtils.findRenderedComponentWithType(tree, Passthrough)
180+
expect(stub.props.string).toBe('')
181+
store.dispatch({ type: 'APPEND', body: 'a' })
182+
expect(stub.props.string).toBe('a')
183+
})
184+
137185
it('should handle dispatches before componentDidMount', () => {
138186
const store = createStore(stringBuilder)
139187

0 commit comments

Comments
 (0)