Skip to content

Commit bb9b86c

Browse files
mwilc0xtimdorr
authored andcommitted
throw if getState, subscribe, or unsubscribe called while dispatching (#1569)
* throw error if getState, subscribe, or unsubscribe called while dispatching * prevent throwing if not subscribed * update getState error message * fix space after period * update subscribe/unsubscribe error messages
1 parent e8d3bba commit bb9b86c

File tree

5 files changed

+129
-3
lines changed

5 files changed

+129
-3
lines changed

src/createStore.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ export default function createStore(reducer, initialState, enhancer) {
6262
* @returns {any} The current state tree of your application.
6363
*/
6464
function getState() {
65+
if (isDispatching) {
66+
throw new Error(
67+
'You may not call store.getState() while the reducer is executing. ' +
68+
'The reducer has already received the state as an argument. ' +
69+
'Pass it down from the top reducer instead of reading it from the store.'
70+
)
71+
}
72+
6573
return currentState
6674
}
6775

@@ -93,6 +101,15 @@ export default function createStore(reducer, initialState, enhancer) {
93101
throw new Error('Expected listener to be a function.')
94102
}
95103

104+
if (isDispatching) {
105+
throw new Error(
106+
'You may not call store.subscribe() while the reducer is executing. ' +
107+
'If you would like to be notified after the store has been updated, subscribe from a ' +
108+
'component and invoke store.getState() in the callback to access the latest state. ' +
109+
'See http://redux.js.org/docs/api/Store.html#subscribe for more details.'
110+
)
111+
}
112+
96113
var isSubscribed = true
97114

98115
ensureCanMutateNextListeners()
@@ -103,6 +120,13 @@ export default function createStore(reducer, initialState, enhancer) {
103120
return
104121
}
105122

123+
if (isDispatching) {
124+
throw new Error(
125+
'You may not unsubscribe from a store listener while the reducer is executing. ' +
126+
'See http://redux.js.org/docs/api/Store.html#subscribe for more details.'
127+
)
128+
}
129+
106130
isSubscribed = false
107131

108132
ensureCanMutateNextListeners()

test/createStore.spec.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import expect from 'expect'
22
import { createStore, combineReducers } from '../src/index'
3-
import { addTodo, dispatchInMiddle, throwError, unknownAction } from './helpers/actionCreators'
3+
import {
4+
addTodo,
5+
dispatchInMiddle,
6+
getStateInMiddle,
7+
subscribeInMiddle,
8+
unsubscribeInMiddle,
9+
throwError,
10+
unknownAction
11+
} from './helpers/actionCreators'
412
import * as reducers from './helpers/reducers'
513

614
describe('createStore', () => {
@@ -451,6 +459,31 @@ describe('createStore', () => {
451459
).toThrow(/may not dispatch/)
452460
})
453461

462+
it('does not allow getState() from within a reducer', () => {
463+
const store = createStore(reducers.getStateInTheMiddleOfReducer)
464+
465+
expect(() =>
466+
store.dispatch(getStateInMiddle(store.getState.bind(store)))
467+
).toThrow(/You may not call store.getState()/)
468+
})
469+
470+
it('does not allow subscribe() from within a reducer', () => {
471+
const store = createStore(reducers.subscribeInTheMiddleOfReducer)
472+
473+
expect(() =>
474+
store.dispatch(subscribeInMiddle(store.subscribe.bind(store, () => {})))
475+
).toThrow(/You may not call store.subscribe()/)
476+
})
477+
478+
it('does not allow unsubscribe from subscribe() from within a reducer', () => {
479+
const store = createStore(reducers.unsubscribeInTheMiddleOfReducer)
480+
const unsubscribe = store.subscribe(() => {})
481+
482+
expect(() =>
483+
store.dispatch(unsubscribeInMiddle(unsubscribe.bind(store)))
484+
).toThrow(/You may not unsubscribe from a store/)
485+
})
486+
454487
it('recovers from an error within a reducer', () => {
455488
const store = createStore(reducers.errorThrowingReducer)
456489
expect(() =>

test/helpers/actionCreators.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { ADD_TODO, DISPATCH_IN_MIDDLE, THROW_ERROR, UNKNOWN_ACTION } from './actionTypes'
1+
import {
2+
ADD_TODO,
3+
DISPATCH_IN_MIDDLE,
4+
GET_STATE_IN_MIDDLE,
5+
SUBSCRIBE_IN_MIDDLE,
6+
UNSUBSCRIBE_IN_MIDDLE,
7+
THROW_ERROR,
8+
UNKNOWN_ACTION
9+
} from './actionTypes'
210

311
export function addTodo(text) {
412
return { type: ADD_TODO, text }
@@ -26,6 +34,27 @@ export function dispatchInMiddle(boundDispatchFn) {
2634
}
2735
}
2836

37+
export function getStateInMiddle(boundGetStateFn) {
38+
return {
39+
type: GET_STATE_IN_MIDDLE,
40+
boundGetStateFn
41+
}
42+
}
43+
44+
export function subscribeInMiddle(boundSubscribeFn) {
45+
return {
46+
type: SUBSCRIBE_IN_MIDDLE,
47+
boundSubscribeFn
48+
}
49+
}
50+
51+
export function unsubscribeInMiddle(boundUnsubscribeFn) {
52+
return {
53+
type: UNSUBSCRIBE_IN_MIDDLE,
54+
boundUnsubscribeFn
55+
}
56+
}
57+
2958
export function throwError() {
3059
return {
3160
type: THROW_ERROR

test/helpers/actionTypes.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
export const ADD_TODO = 'ADD_TODO'
22
export const DISPATCH_IN_MIDDLE = 'DISPATCH_IN_MIDDLE'
3+
export const GET_STATE_IN_MIDDLE = 'GET_STATE_IN_MIDDLE'
4+
export const SUBSCRIBE_IN_MIDDLE = 'SUBSCRIBE_IN_MIDDLE'
5+
export const UNSUBSCRIBE_IN_MIDDLE = 'UNSUBSCRIBE_IN_MIDDLE'
36
export const THROW_ERROR = 'THROW_ERROR'
47
export const UNKNOWN_ACTION = 'UNKNOWN_ACTION'

test/helpers/reducers.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { ADD_TODO, DISPATCH_IN_MIDDLE, THROW_ERROR } from './actionTypes'
1+
import {
2+
ADD_TODO,
3+
DISPATCH_IN_MIDDLE,
4+
GET_STATE_IN_MIDDLE,
5+
SUBSCRIBE_IN_MIDDLE,
6+
UNSUBSCRIBE_IN_MIDDLE,
7+
THROW_ERROR
8+
} from './actionTypes'
29

310

411
function id(state = []) {
@@ -46,6 +53,36 @@ export function dispatchInTheMiddleOfReducer(state = [], action) {
4653
}
4754
}
4855

56+
export function getStateInTheMiddleOfReducer(state = [], action) {
57+
switch (action.type) {
58+
case GET_STATE_IN_MIDDLE:
59+
action.boundGetStateFn()
60+
return state
61+
default:
62+
return state
63+
}
64+
}
65+
66+
export function subscribeInTheMiddleOfReducer(state = [], action) {
67+
switch (action.type) {
68+
case SUBSCRIBE_IN_MIDDLE:
69+
action.boundSubscribeFn()
70+
return state
71+
default:
72+
return state
73+
}
74+
}
75+
76+
export function unsubscribeInTheMiddleOfReducer(state = [], action) {
77+
switch (action.type) {
78+
case UNSUBSCRIBE_IN_MIDDLE:
79+
action.boundUnsubscribeFn()
80+
return state
81+
default:
82+
return state
83+
}
84+
}
85+
4986
export function errorThrowingReducer(state = [], action) {
5087
switch (action.type) {
5188
case THROW_ERROR:

0 commit comments

Comments
 (0)