I am writing a custom middleware that needs to dispatch thunk actions. The problem is that the middleware is called after redux-thunk
in the middleware chain,
It turns out the issue was in my store configuration. Using redux's compose
caused the issue.
before:
import {createStore, applyMiddleware, compose} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../redux/reducers';
import webrtcVideoMiddleware from '../redux/middleware/webrtcVideo';
import bugsnagErrorCatcherMiddleware from '../redux/middleware/bugsnag/errorCatcher';
import bugsnagbreadcrumbLoggerMiddleware from '../redux/middleware/bugsnag/breadcrumbLogger';
import * as APIFactory from '../services/APIFactory';
import Pusher from '../services/PusherManager';
const PusherManager = new Pusher(false);
export default function configureStore(initialState) {
return createStore(rootReducer, initialState, compose(
applyMiddleware(bugsnagErrorCatcherMiddleware()),
applyMiddleware(thunk.withExtraArgument({APIFactory, PusherManager})),
applyMiddleware(webrtcVideoMiddleware(PusherManager)),
applyMiddleware(bugsnagbreadcrumbLoggerMiddleware())
));
}
after:
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../redux/reducers';
import webRTCVideoMiddleware from '../redux/middleware/webrtcVideo';
import bugsnagErrorCatcherMiddleware from '../redux/middleware/bugsnag/errorCatcher';
import bugsnagBreadcrumbLoggerMiddleware from '../redux/middleware/bugsnag/breadcrumbLogger';
import * as APIFactory from '../services/APIFactory';
import Pusher from '../services/PusherManager';
const PusherManager = new Pusher(false);
export default function configureStore(initialState) {
const middleware = [
bugsnagErrorCatcherMiddleware(),
thunk.withExtraArgument({APIFactory, PusherManager}),
webRTCVideoMiddleware.withExtraArgument(PusherManager),
bugsnagBreadcrumbLoggerMiddleware(),
];
return createStore(rootReducer, initialState, applyMiddleware(...middleware));
}
Dispatching inside the middleware chain will send the action to the start of the middleware chain, and will call the thunk as usual (Demo - look at the console).
Why?
The original store.dispatch()
(before applying middlewares) checks if the action is a plain POJO, and if not throws an error:
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
When you applyMiddleware()
the dispatch
is replaced by a new method, which is the chain of middleware, that call the original store.dispatch()
in the end. You can see it in the applyMiddleware method:
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch // dispatch is now the original store's dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action) // this refers to the dispatch variable. However, it's not the original dispatch, but the one that was created by compose
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch) // dispatch is a composition of the chain, with the original dispatch in the end
return {
...store,
dispatch
}
}
}
btw - change your middleware to this, since the 1st function will prevent your middleware from working.
export default const createMiddleware = ({dispatch, getState}) => next => (action) => {
if(action.type !== 'FOO') {
return next(action);
}
dispatch(thunkActionHere); // this is the issue
}