Dispatch to beginning of middleware chain?

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, so I get the error Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. when using the provided dispatch .

export default function createMiddleware() {
    return ({dispatch, getState}) => next => (action) => {
        if(action.type !== 'FOO') {
            return next(action);
        }

        dispatch(thunkActionHere); // this is the issue
    }
}

I would like to dispatch this thunk action back to the beginning of the middleware chain so that redux-thunk can handle it. Is this possible?

update:

function createMiddleware(extraArgument) {
    return function ({dispatch, getState}) {
        return function (next) {
            return function (action) {
                switch (action.type) {
                    case 'FOO1':
                        dispatch({type: 'NORMAL_ACTION'}); // works fine
                        break;
                    case 'FOO2':
                        dispatch(function() {
                            return (dispatch, getState) => { // Error: Actions must be plain objects. Use custom middleware for async actions.
                                console.log('inside the thunk');
                            };
                        });
                        break;
                    default:
                        return next(action);
                }
            };
        };
    };
}

const middleware = createMiddleware();
middleware.withExtraArgument = createMiddleware;

export default middleware;

Here's my store configuration:

export default function configureStore(initialState) {
    const store = createStore(rootReducer, initialState, compose(
        // Add other middleware on this line...
        applyMiddleware(bugsnagErrorCatcherMiddleware()),
        applyMiddleware(thunk.withExtraArgument({APIFactory, PusherManager})),
        applyMiddleware(webrtcVideoMiddleware.withExtraArgument(PusherManager)), // this is the middleware above
        applyMiddleware(bugsnagbreadcrumbLoggerMiddleware()),
        )
    );

    return store;
}

I cannot put my middleware before redux-thunk because then it doesn't receive actions that thunks dispatch.


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
}

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));
}

We use composeWithDevTools from redux-devtools-extension. Same issue and same solution as noted above. Just needed to move to using applyMiddleware(...middlewares) instead of multiple applyMiddleware(middleware), applyMiddleware(middleware) as arguments to the composition.

链接地址: http://www.djcxy.com/p/59058.html

上一篇: Thunk而不是定制的中间件?

下一篇: 调度到中间件链的开始?