mangocaptain mangocaptain - 3 months ago 20
React JSX Question

What does it mean for an action to travel through an entire middleware chain in redux?

Looking at the react-redux docs, I don't get the below how about why having an action travel the whole middleware would be useful:


It does a bit of trickery to make sure that if you call
store.dispatch(action) from your middleware instead of next(action),
the action will actually travel the whole middleware chain again,
including the current middleware. This is useful for asynchronous
middleware, as we have seen previously.


What does it mean for an action to travel through the middleware? How does that affect the dispatch? My understanding was that the dispatch changes through each layer of middleware it goes through, and
next
refers to the previous middlware's dispatch, whereas
dispatch
refers to the original store.dispatch.

Answer

As you can see in the the middleware example, there are multiple middleware items that create a pipe:

rafScheduler -> timeoutScheduler -> thunk -> vanillaPromise -> etc...

An action travels all middleware items before getting to the base reducer or being intercepted by one of the middleware items. Each middleware item can decide to move an action to the next middleware in the chain by using next(). However, sometimes we want the action to travel the chain from the start.

For example, using redux thunk, we dispatch an async action, that will be handled by the thunk middleware. The async action will dispatch another action, when the async call succeeds. This action should start again with the rafScheduler middleware.

If dispatch would have worked like next, it would travel to the vanillaPromise middleware instead. To solve that, dispatch(action), no matter where it was called, always travels the chain from the start.

To create this behavior applyMiddleware() runs over the middleware store => next => action methods, passes the middlewareAPI api to the store param, passes, and overrides the store.dispatch, with a new dispatch that is the combined middleware. This is where the magic happens - the new dispatch is the chain of middleware methods, where each calls the one after it when next is invoked (next = the next middleware method), and the last ones next() is the old store.dispatch that calls the base reducer:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch // this is the original dispatch
    var chain = []

    /*** this the store param of the middleware ***/
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action) 
    }

    /*** store param is applied to old middlewares to create the chain ***/
    chain = middlewares.map(middleware => middleware(middlewareAPI))

    /*** The chain is composed. For all methods in the chain, but the last, next() is the middleware method after it in the chain, the last next is store.dispatch ***/
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store, 
      dispatch // the new dispatch is returned instead of the old
    }
  }
}
Comments