A Beginner's Guide to Redux Middleware

Published May 16, 2016Last updated Jan 18, 2017

Redux has become the state container of choice for React apps. The key idea that makes redux so popular is that your application logic lives in "reducers", which are JavaScript functions that take in a state and an action, and return a new state. Reducers are pure functions: they don't rely on or modify any global state, so they're easy to test, reason about, and refactor. For example, here's a redux store that keeps track of a counter:

Reducers are an elegant tool for managing state, but they're not the full story when it comes to building applications with redux. If you look carefully, you'll notice 2 implicit constraints on what redux reducers can do:

Reducers must be synchronous. They return the new state.

Because reducers should not modify global state, reducers should not use functions like setInterval()

Let's suppose you wanted to use redux as a state container for a stopwatch application: the application should be able to display the elapsed time on the screen and then save the elapsed time to a server using an HTTP request. Reducers are great for transforming state due to synchronous actions like button presses, but what happens when you need to throw some asynchronous behavior into the mix? That's where the idea of middleware comes in.

Introducing Middleware

Your stopwatch application needs the ability to display the amount of time elapsed. The right way to do this is for your reducer to listen for 3 actions:

START_TIMER, fired when the timer starts

TICK, fired when you should change the current amount of time elapsed

STOP_TIMER, fired when you're not going to receive any more TICK actions.

The above function is just a plain old reducer: it doesn't rely on or modify global state, and it's fully synchronous. When the user clicks a button to start the timer, you can dispatch the START_TIMER event, and when they click a button to stop the timer, you dispatch STOP_TIMER.

However, there's a problem: you need to periodically dispatch TICK events to update the elapsed time. You can call setInterval() in the START_TIMER case statement, but then your reducer modifies global state and you violate redux best practices. The right place to periodically dispatch TICK events is in middleware:

The redux middleware syntax is a mouthful: a middleware function is a function that returns a function that returns a function. The first function takes the store as a parameter, the second takes a next function as a parameter, and the third takes the action dispatched as a parameter. The store and action parameters are the current redux store and the action dispatched, respectively. The real magic is the next() function. The next() function is what you call to say "this middleware is done executing, pass this action to the next middleware". In other words, middleware can be asynchronous.

The timerMiddleware function above is responsible for managing the setInterval() function, including clearing the interval when the STOP_TIMER action is dispatched. Redux calls the timerMiddleware function when a new action is dispatched, before the reducer. This means the middleware can transform actions as necessary, including dispatching new actions. When you run the above code in Node.js, you should see approximately the below output.

Remember that middleware can be asynchronous. The next() function is middleware's flow control mechanism: it's how you defer control to the next middleware in the chain. You can call next() asynchronously, or even not at all. You can create a middleware that resolves promises for you. If you dispatch an action with a payload property that's a promise, the below middleware will wait for that promise to resolve or reject before calling next().

How does this help with HTTP requests in redux? HTTP clients like superagent return promise-compatible interfaces, so if you set an action's payload to a superagent request, the promiseMiddleware will wait for the request to complete before passing the action along.

In the redux paradigm, your reducer should be responsible for any modifications to the state. Your middleware should not modify the state. However, your middleware should be responsible for any interactions that affect global state (like setInterval()) or any asynchronous operations (like HTTP requests).

Other Middleware Applications

While resolving promises is the most common use case for middleware, there are numerous other use cases for middleware.

Next Steps

Middleware is essential for building any non-trivial redux application. Any asynchronous behavior or global state modifications should go through middleware, so your reducers can be pure functions. If you're interested in learning more about how to use middleware to build real-world redux applications, check out this sample application, which is a medium clone written in React with Redux as the state container. This app is the basis for an upcoming video course on Thinkster about Redux, so sign up on GitHub for updates!