Before we get to redux-thunk
specifically, we want to solidify our understanding of how middleware fits into Redux’s data flow. Let’s explore how middleware actually gets invoked in Redux, so that we know how a middleware should be structured. After that, we’re going to write a simple middleware from scratch.
But first, you’ll recall from the previous exercise that middleware runs after an action is dispatched and before that action is passed along to the reducer. How does this actually work?
To add a middleware to our project, we use Redux’s applyMiddleware
function like so.
import { createStore, applyMiddleware } from 'redux'; import { middleware1, middleware2, middleware3 } from './exampleMiddlewares'; import { exampleReducer } from './exampleReducer'; import { initialState} from './initialState'; const store = createStore( exampleReducer, initialState, applyMiddleware( middleware1, middleware2, middleware3 ) );
The specifics of how applyMiddleware
works are outside the scope of this lesson. All you need to know is that once middleware has been added to a Redux project, calls to dispatch
are actually calls to the middleware pipeline (the chain of all added middlewares). This means that any actions we dispatch will be passed from middleware to middleware before they hit an app’s reducers.
Middlewares must conform to a specific, nested function structure in order to work as part of the pipeline (this nested structure is also called a higher-order function, if you’d like to read more). That structure looks like this:
const exampleMiddleware = storeAPI => next => action => { // do stuff here return next(action); // pass the action on to the next middleware in the pipeline }
Each middleware has access to the storeAPI
(which consists of the dispatch
and getState
functions), as well as the next
middleware in the pipeline, and the action
that is to be dispatched. The body of the middleware function performs the middleware’s specific task before calling the next middleware in the pipeline with the current action (note that if the middleware is the last in the pipeline, then next
is storeAPI.dispatch
so calling next(action)
is the same as dispatching the action to the store).
Now let’s write a custom middleware that logs the contents of our store to the console.
Instructions
In the code editor, you’ll notice we’ve created a simple reducer for you, and taken care of importing Redux’s createStore
and applyMiddleware
functions. We’ve created a store by calling createStore
and passing it the reducer. Since all Redux middleware have the same basic structure, you can start by copying this snippet:
const logger = storeAPI => next => action => { // do stuff here return next(action); };
Replace the comment // do stuff here
, with a line of code that logs the contents of the store to the console. Remember, you can access the store’s state with storeAPI.getState()
.
Instead of returning next(action)
, store the result of that function call in a const
called nextState
. Next, log nextState
to the console. Finally, return nextState
.
Apply your custom middleware to your store by adding a third argument to the call to createStore
. This argument should be the result of calling applyMiddleware
with the logger
middleware you’ve written.
Dispatch the following action to your store:
{ type: 'NEW_MESSAGE', payload: 'I WROTE A MIDDLEWARE' }
Note that the store’s new state was logged to the console. Congrats – you just wrote your first middleware!