Search code examples
javascriptreactjsreduxredux-toolkit

How to create a middleware to return multiple middleware in Redux Toolkit?


I have set up Redux Toolkit in my React App and I ended up with an issue. I have my middleware.js file which has multiple middleware, and I have store.js which has the store configs. I need to export all the middleware inside the middleware.js at once, so I can concat it in the store.js without having multiple concats (or a big array inside a single concat). I created the following sample and, I thought it was working. The problem is with toastMiddleware function. It looks like this middleware runs twice. The console.log() runs only once, but it adds two toast messages at once. When I concatenate two middleware functions separately in the store, this problem doesn't happen. So I'm not sure if I created this combined middleware correctly. Any help?

middleware.js

import { addError } from "../error.slice";
import { addToast } from "../toast.slice ";

const apiErrorMiddleware = (api) => (next) => (action) => {
    if (action.type.endsWith('/rejected') && action.payload && action?.payload?.status != 401) {
        api.dispatch(addError({ response: action.payload, meta: action.meta, arg: action.error }));
    }
    return next(action);
};

const toastMiddleware = (api) => (next) => (action) => {
    const mutationVerb = action?.payload?.created?.length ? 'Created' : action?.payload?.updated?.length ? 'Updated' : action?.payload?.upserted?.length ? 'Upserted' : null;

    if (action.type.endsWith('executeMutation/fulfilled')) {
        console.log('toast mid', action)
        if (mutationVerb)
            api.dispatch(addToast({title: mutationVerb, content: 'Operation completed successfully.', variant: 'success'}));
        else
            api.dispatch(addToast({title: 'No Change', content: 'Operation completed successfully.', variant: 'info'}));
    }

    return next(action);
};

export const combinedMiddleware = (api) => (next) => (action) => [
    apiErrorMiddleware(api)(next)(action),
    toastMiddleware(api)(next)(action),
];

store.js

import { configureStore } from '@reduxjs/toolkit';
import api from './api.slice';
import errorSlice from './error.slice';
import { combinedMiddleware } from './middleware/apiMiddleware';
import toastSlice from './toast.slice ';

export const store = configureStore({
    reducer: {
      [api.reducerPath]: api.reducer,
      errorSlice: errorSlice,
      toastSlice: toastSlice,
    },
    middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false })
        .concat([api.middleware, combinedMiddleware])
});

Solution

  • I don't quite understand what the issue or objection is to adding the middleware to the store normally, but if you are just wanting to define an array of middleware elsewhere to be added to the store then I suggest simplifying the code to do just that instead of creating a custom middleware function that calls other middleware functions. Typically the middlwares are a flat list of functions, each one doing their work and then calling the next middleware in the "chain" and returning the result.

    Example:

    export const middleware = [
      apiErrorMiddleware,
      toastMiddleware,
    ];
    

    Spread these into the concat method:

    import { configureStore } from '@reduxjs/toolkit';
    import api from './api.slice';
    import errorSlice from './error.slice';
    import { middleware } from './middleware/middleware';
    import toastSlice from './toast.slice ';
    
    export const store = configureStore({
      reducer: {
        [api.reducerPath]: api.reducer,
        errorSlice: errorSlice,
        toastSlice: toastSlice,
      },
      middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false })
        .concat([api.middleware, ...middleware]),
    });