Why is dispatch returning a promise with no result? (redux middleware)

358 Views Asked by At

I came into a React project that uses vanilla Redux with middleware. The way it is setup is as follows:

composeEnhancers(applyMiddleware(...middleware.map(f => f(services))))

Now middlware is an array of, well, middleware containing functions. services is an object containing external services that are injected into the middlware functions (api and so on).

The interesting part is the middleware, here is a sample of it:

...
const throwErrorFlow = ({ api }) => ({ dispatch, getState }) => next => async (action) => {
  next(action)
  if (action.type === actions.THROW_ERROR) {
    try {
      dispatch(actions.setLoadingSlot({ state: false, context: action.payload.context }))

      const context = getState().ui.context

      const payload = { location: action.payload.location, error: action.payload.error?.stack, context }
      console.log(payload);
      await api.context.throwError(payload)
      dispatch(actions.setErrorModalVisibility({ payload, visibility: true }))
    } catch (error) {
      console.log(error);
    }
  }
}


const middleware = [
  middlwareFunction1,
  middlwareFunction2,
  ...
  throwErrorFlow
]

export default middleware

Then I created my own test action that returns a test string. I added a similar middlware function as the rest. When dispatching this test action from the UI and logging its result, all I get is: Promise {<fulfilled>: undefined}

So I tried zooming in a bit. My action is just the following:

export const customAction = payload => ({
  type: CUSTOM_ACTION,
  payload: payload,
})

And my bit in the middleware is the following:

const customAsyncActionFlow = () => storeAPI => () => action => {
  if (action.type === actions.CUSTOM_ACTION) {
    console.log(action);
    return 'TEST!'
  }
}

const middleware = [
  middlwareFunction1,
  middlwareFunction2,
  ...
  throwErrorFlow,
  customActionFlow 
]

export default middleware

And I call it from the UI as:

console.log(dispatch(customAction('Hello World!')));

My action is logged correctly to the console, but then I get Promise {<fulfilled>: undefined} instead of 'TEST!'. So I removed all other middleware functions and only kept my customActionFlow, and everything worked as I expected. Where is this Promise with no result coming from? Yes all other middleware functions do not return anything, they just modify the state. Does this have to do with this fact? And how do I 'fix' this?

EDIT: okay so I seem to understand what is going on. For each action that requires interaction with the api, a middleware is written for this action which gets applied. In the end there are 20 middleware functions all culminating with the async action for each one. The action that I defined with the test middleware that returns a value gets "lost" in the mix I guess? I am still not sure as to why my return has no effect whatsoever. Is there a way to make my dispatch action call the my test middleware exclusively while keeping all other middlewares applied?

1

There are 1 best solutions below

0
On

Oh dear. While this isn't a direct answer to your question...

I've seen that style of "write all Redux logic as custom middleware" tried a few times... and it is a bad idea!

It makes things highly over-complicated, and adding all these extra middleware for individual chunks of functionality adds a lot of overhead because they all have to run checks for every dispatched action.

As a Redux maintainer I would strongly recommend finding better approaches for organizing and defining the app logic. See the Redux Style Guide for our general suggestions:

Now, as for the actual question:

When you call store.dispatch(someAction), the default behavior is that it returns the action object.

When you write a middleware, that can override the return value of store.dispatch(). A common example of this is the redux-thunk middleware, which just does return thunkFunction(dispatch, getState). This is commonly used to let thunks return promises so that the UI knows when some async logic is complete:

In this case, the middleware is itself defined as an async function, and every async function in JS automatically returns a promise. So, just having one async middleware in the chain is going to end up returning a promise from store.dispatch(anything). (This would be another reason to not write a bunch of logic directly in a custom middleware like that.)