Redux-toolkit: Any plans for improving custom middleware?

Created on 12 Aug 2020  路  10Comments  路  Source: reduxjs/redux-toolkit

First of all, thanks for this amazing package. It has made my life so much easier! :)

I'm working in a legacy redux typescript codebase with lots of custom middlewares:

const customMiddleware: Middleware = (store) => (next) => (action) => {
    switch (action.type) {
        case ActionTypes.CUSTOM_TYPE:
            next(action);

            // custom action

            break;
        default:
            return next(action);
    }
};

My issue with that is the typings. Neither actions nor the store are correctly typed. I tried typing the customMiddleware like this:

image

As you can see this results in a circular reference error. I couldn't find anything in the documentation on how to handle this better.

Now my question: Is there a recommended way for handling custom middleware or are there plans for adding a utility for improving the custom middleware "experience"? Something like createMiddleware, with good action typing like createReducer with the builder pattern.

Most helpful comment

Ah yes, sorry. While dispatch is a Dispatch<AnyAction>, action itself can be any. After all, a middleware can handle all kinds of stuff, e.h. a Function in the case of redux-thunk or a Promise in the case of redux-promise.

All 10 comments

You may need to switch to defining AppState = ReturnType<typeof rootReducer> and create your reducer separately using combineReducers to break that cycle.

Given that a custom middleware needs to be defined separately from the store, I'm not sure there's much we can do about this one for now, but @phryneas might have a better suggestions.

My rootReducer is already the result of combineReducers. Is that what you meant?

I also tried inferring the type directly from the rootReducer, like you said, but now my AppDispatch is a circular reference.

For the RootState what @markerikson says.

As for AppDispatch: AppDispatch is directly derived from the DispatchExt property of all collected middlewares, so it cannot be known before all middlewares are specified, which will be impossible here. Can you go with the normal Dispatch or ThunkDispatch?

Typing it like this works for me:

export type AppState = ReturnType<typeof rootReducer>;
export type AppDispatch = typeof rootStore.dispatch;
export type AppThunk = ThunkAction<void, AppState, unknown, Action<string>>;
export type AppMiddleware = Middleware<{}, AppState, Dispatch & Parameters<AppThunk>[0]>;

export type MiddlewareStore = Parameters<AppMiddleware>[0];

const customMiddleware: AppMiddleware = (store) => (next) => (action) => {
    switch (action.type) {
        case 'CUSTOM_ACTION':
            next(action);

            customSideEffect(store);
            break;
        default:
            return next(action);
    }
};

function customSideEffect(store: MiddlewareStore) {
    // ...
}

The store is now typed - big improvement! :)

Any idea how I could do a type assertion of the action in the middleware?

const customAction = createAction("CUSTOM_ACTION")

const customMiddleware: AppMiddleware = (store) => (next) => (action) => {
    switch (action.type) {
        case customAction.type:
            // how to assert action as customAction here?

            next(action);

            customSideEffect(store);
            break;
        default:
            return next(action);
    }
};

if (customAction.match(action)) {

Damn it. Totally forgot that existed! ^^

Thanks for your help guys!

Is it expected to action argument in middleware function type to be of type any? I thought it should be at least Action<any>?

@Anrock Yes, Action<any> or just AnyAction. How do you come to the any conclusion?

@phryneas well, I just wrote a const myMiddleware: Middleware = (api) => (next) => (action) => { blah-blah } and checked a type of action - it was any.

Ah yes, sorry. While dispatch is a Dispatch<AnyAction>, action itself can be any. After all, a middleware can handle all kinds of stuff, e.h. a Function in the case of redux-thunk or a Promise in the case of redux-promise.

Was this page helpful?
0 / 5 - 0 ratings