Custom Redux Middleware - Dispatch to beginning of middleware chain?

时光毁灭记忆、已成空白 提交于 2019-12-31 03:13:24

问题


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. 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.


回答1:


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
}



回答2:


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



回答3:


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.



来源:https://stackoverflow.com/questions/42217369/custom-redux-middleware-dispatch-to-beginning-of-middleware-chain

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!