Alexander Gnetov
Alexander Gnetov

Reputation: 11

Redux Toolkit middleware does not work to handle successful asynchronous actions

I've integrated into my Next.js app some of my own middleware into the Redux-Toolkit store using configureStore. Middleware is designed to handle asynchronous actions such as fetchAccounts/fulfilled to update navigation state and user accounts. Despite adding them via

getDefaultMiddleware({
  thunk: {
    extraArgument: updateNavTabMiddleware, updateSelectedAccountMiddleware
  },
  serializableCheck: false,
}),

they seem to be greyed out. I used console.log to debug incoming actions, but I don't see the expected logs. How to properly debug middleware in the Redux-Toolkit, and is there a way to ensure that they are actually executed in the expected order before the reducers?

In the documentation I read that the Redux-Toolkit configureStore API automatically adds the Thunk middleware during store creation, so it should typically be available with no extra configuration needed.

Middleware

import { PayloadAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';

export const updateSelectedAccountMiddleware = (store: MiddlewareAPI) => (next: Dispatch) => (action: PayloadAction<Account[] | number>) => {
  console.log(action);
  if (action.type === 'accounts/fetchAccounts/fulfilled' && Array.isArray(action.payload)) {
    const savedAccountId = getSelectedAccountIdLS();
    const savedAccount = action.payload.find((account: Account) => account.id === Number(savedAccountId));

    store.dispatch(setSelectedAccount(savedAccount || action.payload[0]));
  }

  if (action.type === 'accounts/deleteAccount/fulfilled' && typeof(action.payload) === 'number') {
    const state = store.getState();
    let newAccounts = state.accounts.data;

    if (state.accounts.selected.id === action.payload) {
      newAccounts = state.accounts.data.filter((item: Account) => item?.id !== action.payload);
    }

    saveSelectedAccountIdLS(newAccounts[0].id);
    store.dispatch(setSelectedAccount(newAccounts[0]));
  }

  return next(action);
};

Store

export const makeStore = () => {
  return configureStore({
    reducer: {
      visual: visualReducer,
      user: userReducer,
      accounts: accountsReducer,
      topics: topicsReducer,
      channelsHistoryRecords: channelsHistoryRecordsReducer,
      competitors: competitorsReducer,
      words: wordsReducer
    },
    middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      thunk: {
        extraArgument: updateNavTabMiddleware, updateSelectedAccountMiddleware
      },
      serializableCheck: false,
    }),
  })
}

// Infer the type of makeStore
export type AppStore = ReturnType<typeof makeStore>
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppStore: () => AppStore = useStore

Upvotes: 1

Views: 314

Answers (1)

Drew Reese
Drew Reese

Reputation: 202605

It appears you are only passing these middleware functions to the thunk middleware as its "extra argument". While it is true that Redux-Toolkit applies Thunk middleware by default, the Thunk middleware's extraArgument doesn't add any additional middleware to the store. The extraArgument is made available on createAsyncThunk's thunkAPI of the payloadCreator function.

If you are trying to add additional middlewares to your store then they should be appended/prepended to the middleware of the store when it's created.

See configureStore middleware and getDefaultMiddleware documentation for complete details.

Add your updateNavTabMiddleware and updateSelectedAccountMiddleware middleware functions to the store's middleware when creating/configuring it.

Store

const reducer = {
  visual: visualReducer,
  user: userReducer,
  accounts: accountsReducer,
  topics: topicsReducer,
  channelsHistoryRecords: channelsHistoryRecordsReducer,
  competitors: competitorsReducer,
  words: wordsReducer
};

export const makeStore = () => {
  return configureStore({
    reducer,
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({ serializableCheck: false })
        .concat(updateNavTabMiddleware, updateSelectedAccountMiddleware),
  });
};

Upvotes: 1

Related Questions