J.E.C.
J.E.C.

Reputation: 3012

How does "next" work in react-redux middleware?

I'm new to React and Redux. I have a simple app where you can add input text to an unordered list. I have middleware that prevents specific words from being displayed. I don't understand the order of operations that implement middleware. From what I understand, when I trigger a dispatch, what happens is something along the lines of:

  1. Trigger dispatch from form submit event
// title is an object with a string
function handleSubmit(event) {
    props.addArticle(title);
}
  1. Go through middleware function:
// store.js
const store = createStore(
  rootReducer, // this is defined elsewhere and effectively handles actions
  applyMiddleware(forbiddenWordsMiddleware)
);
// middleware.js
import { ADD_ARTICLE } from "../constants/constants.js";
const forbiddenWords = ["spam", "money"];
export function forbiddenWordsMiddleware({ dispatch }) {
    return function (next) {
        return function (action) {
            if (action.type === ADD_ARTICLE) {
                const foundWord = forbiddenWords.filter((word) =>
                    action.payload.title.includes(word)
                );
                if (foundWord.length) {
                    return dispatch({ type: "FOUND_BAD_WORD" });
                }
            }
            return next(action);
        };
    };
}

How does the above function actually work with createStore and applyMiddleware? Especially confusing to me is the next(action) line. Where do next and action come from? I'm having trouble visualizing the linear execution from form submit to checking for forbidden words.

Upvotes: 6

Views: 6328

Answers (2)

Eddie Cooro
Eddie Cooro

Reputation: 1968

In this context, next simply means that this middleware is not interested in that particular action, and wants to pass it to the other middlewares to take care of it.

When you give your middleware to the applyMiddleware function, it first calls each middleware and passes it something called middlewareAPI here. Then starts with the last middleware, and calls it and gives the store.dispatch to it then sends the result of it to the middleware, which comes before the last one. And continues like this (passing the result of the latter middleware to the middleware that comes before it) all the way to the first middleware.

So let's say you have these three middlewares: [a, b, c] and you pass all of them to the applyMiddleware function. here is what happens:

  1. First, every middleware gets called with the middlewareAPI object, which basically is an object containing the original store.dispatch and store.getState.

    const middlewareAPI = { getState: store.getState, dispatch: store.dispatch }
    const aInChain = a(middlewareAPI);
    const bInChain = b(middlewareAPI);
    const cInChain = c(middlewareAPI);
    
  2. Second, we call every middleware with the result of the previous call of the middleware that comes after it. Expect for the last middleware that doesn't have anything after it and gets the original dispatch as input.

    const finalC = cInChain(store.dispatch);
    const finalB = bInChain(finalC);
    const finalA = aInChain(finalB);
    
  3. We then set the final version of the first middleware, as the dispatch of the new enhanced store. So for example, when you call store.dispatch({ type: "Hello" }), the finalA function gets called, and if it calls the finalB function that is provided to it as next, then the next middleware in the chain gets called with whatever action the finalA gives to it. And it goes like this for the whole chain.

So in the example that we have in the question, there are two return statements in the middleware. The first one is the return dispatch({...}) and the second one is return next({...}).

In both of them, our middleware says that it has finished its job with this action and lets the dispatch chain continue; But in the first return statement, the middleware calls the original dispatch directly and passes a new action with a new type to the dispatch. So the action that has been passed to our middleware initially, will get completely forgotten. This statement breaks the chain of the middlewares, before the action reaches the store, and starts a new dispatch with a new action.

In the second return, our middleware calls the next function, which as I described before, is the next middleware in the row, and our middleware sends the original action to it without changing anything. So the result would be like when this middleware didn't exist at all.

In this scenario, our middleware merely says "I don't care about what this action is; I want to send it through to the next middlewares to decide about whether it should reach the store.dispatch or not".

Upvotes: 8

markerikson
markerikson

Reputation: 67469

Middleware form a pipeline around the actual Redux store.dispatch function. When you call store.dispatch(action), you're actually calling the first middleware in the chain. Inside of the middleware, next passes a value to the next middleware, while storeAPI.dispatch restarts the pipeline. action is the value that was passed in to dispatch (or passed to next from the previous middleware).

This visualization may help:

Redux middleware pipeline diagram

The actual implementation is admittedly a bit of magical code, but not something you should need to worry about.

For more details, see the "Extensibility and middleware" section of my "Redux Fundamentals Workshop" slides, as well as these articles on how Redux middleware work.

Upvotes: 10

Related Questions