-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Description
Bug
An important middleware pattern has been broken. IMO the majority of middleware solutions encapsulate their own concerns and are independent of the existence of other middleware. A change was made recently to break a pattern that only caused a minor issue for a specific subset of middleware: those that exhibit different behaviour when interacting with other middleware during the applyMiddleware
initialisation path.
Here are some examples:
// Make state aware of browser media queries
const mediaQueries = (mediaQueries = defaultMediaQueries): Middleware => store => {
if (typeof window !== 'undefined') {
const reverseMap = createReverseMap(mediaQueries)
const handleMediaChange = ({media, matches}: MediaChangedActionPayload) =>
store.dispatch(mediaChanged(reverseMap[media], matches))
const addMatchListener = (media: string) => {
const match = window.matchMedia(media)
match.addListener(handleMediaChange)
return match.matches
}
values(mediaQueries).forEach(media =>
handleMediaChange({media, matches: addMatchListener(media)}))
}
return next => (action: any) => next(action)
}
Another example:
// Make state aware of user adblockers
const adBlockDetection: Middleware = store => {
if (typeof document !== 'undefined' && !document.getElementById('XMpADSwgbPUC')) {
store.dispatch({type: AD_BLOCKER_DETECTED, payload: true})
}
return next => (action: any) => next(action)
}
Another example:
// Make state aware of socket connectivity and allow synchronisation of actions
const socketMiddleware = ({actionCreator, url, dataSync}) => store => {
const socket = new WebSocket(url.replace(/(http|https)/, 'ws'))
socket.addEventListener('message', ({data}) => {
store.dispatch(JSON.parse(data))
})
socket.addEventListener('open', () => {
socket.send(syncCreator(CONNECT_TO_DOMAIN, store.getState(), null, socket))
store.dispatch(actionCreator({connected: true}))
})
socket.addEventListener('close', reason => {
store.dispatch(actionCreator({connected: false}))
})
return next => action => {
if (dataSync.hasOwnProperty(action.type)) {
socket.send(syncCreator(action.type, store.getState(), action.payload, socket))
}
return next(action)
}
}
What is the current behaviour?
The following error is thrown during applyMiddleware
, essentially breaking the application:
Uncaught Error: Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch
Refactoring the above middleware to be closed over by the next =>
function (like below) makes no difference (but is obviously not a viable solution either).
// Still broken:
const mediaQueries = (mediaQueries = defaultMediaQueries): Middleware => store => next => {
// ...middlewareCodeGoesHere...
return (action: any) => next(action)
}
What is the expected behavior?
Do not break the app. Log a warning if you want, so I can ignore it, because in 100% of my use cases I do not care if other middleware can handle that dispatch. I strongly suspect that there are (or should be) very few legitimate reasons why any dispatch from within a middleware context should ever interact with any other middleware in the stack, and if it did, I would consider that a code smell (it would place implicit order dependencies on middleware at the very least).
Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?
Worked until this PR.