Redux From Scratch (Chapter 5 | Implementing Middleware)

Michael Mangialardi
Coding Artist
Published in
9 min readJul 6, 2017

Buy the Official Ebook

If you would like to support the author and receive a PDF, EPUB, and/or MOBI copy of the book, please purchase the official ebook.

Prerequisites

Chapter 1 | Core Concepts
Chapter 2 | Practicing the Basics
Chapter 3 | Implementing With React
Chapter 4 | Async Practice With Twitch API

Scope of This Chapter

We’ve gotten familiar with Redux, and particularly, a React & Redux implementation. However, there are some improvements that we can make. One of those improvements is to incorporate Redux middleware. We will spend the entire chapter explaining what this is and why to use it.

What is Middleware and Why Use It?

What is Middleware?

In the ReactReduxTwitch project from the previous chapter, we had an action creator called FetchRequest that looked like this:

//define action within an action creator
function FetchRequest() {
const FETCH_REQUEST = 'FETCH_REQUEST'
return {
type: FETCH_REQUEST,
status: "loading"
}
}
export default FetchRequest

The action defines the type of action and updates to properties in the state of our application (status in the example shown above).

In our React container component, the action is dispatched by a call to the action creator like so:

this.props.store.dispatch(FetchRequest());

As soon as this action is dispatched, the reducer will handle the action to update the state. Here’s the example of the FETCH_REQUEST action being handled by the reducer:

function TwitchApp(state = initialState, action) {
switch(action.type) {
case 'FETCH_REQUEST':
const requested = Object.assign({}, state, {
status: action.status
})
return requested
//more cases
}
}

What if we wanted to do something after the action is dispatched but before the action is handled by the reducer?

This is the functionality that middleware provides. We can do something in the middle of the action being dispatched and the action being handled by the reducer. Hence, middleware is a fitting name.

We can either use existing libraries that have already written middleware code for us, or we can write our own middleware.

Now that we kind of understand what middleware does, we still need to answer: “Why use it?”

Why Use Middleware?

The best way to answer this question (as well as see how middleware works) is to go through several use cases together. Let’s get to it!

Logging Middleware

One use case is logging the change of a state in your application.

After an action is dispatched, but before it is handled by the reducer, we can log the action type, the current state (also termed previous, think “soon-to-be previous”), the action object (which tells us what properties in the state are about to be changed), and the soon-to-be new state when the action is handled.

Here’s an example log:

Let’s implement a Redux middleware library called redux-logger so we can easily have a log like the one shown above. We will be implementing this on top of our project from Chapter 3 called ReactReduxTwitch. You can retrieve the latest copy of the project on GitHub.

cd into the project root and let’s install redux-logger:

npm install --save redux-logger

Then, we need to import redux-logger and applyMiddleware in index.js:

import { applyMiddleware, createStore } from 'redux';
import logger from 'redux-logger';

Next, we apply the logger as middleware using applymiddleware(logger) when we create our store:

//initialize store
let store = createStore(
TwitchApp,
applyMiddleware(logger)
);

Run npm start, go to the local host and check the console:

In the snapshot above, we are logging the FETCH_REQUEST action, the previous state (technically current state since this is in the middle of a change, think soon-to-be previous), the action object, and what the next state that will be produced when the action is applied.

As expected, we can see that the action only change the status to “loading” in the state.

It’s that easy!

We can now log our Redux applications and make debugging much easier.

Right on!

Thunk Middleware

There’s another use case that fits well with the ReactReduxTwitch project.

Currently, our API request and dispatching are handled in the Streams component class found in Streams.js:

What if we could move the API request and dispatching into an action creator?

Introducing Redux-Thunk

Well, there’s a middleware that will let us do that called redux-thunk. This library allows our action creators to return functions instead of just action objects. This gives us control to write our own code that will occur between the call of the action creator via the dispatch and the action getting to the reducer.

With this library, we can dispatch an action to the reducer based on a condition:

function incrementIfOdd() {
return (dispatch, getState) => {
const { counter } = getState();

if (counter % 2 === 0) {
return;
}

dispatch(increment());
};
}

We could make our actions asynchronous:

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

function increment() {
return {
type: INCREMENT_COUNTER
};
}

function incrementAsync() {
return dispatch => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}

We could also perform several dispatches within one action creator.

Let’s implement redux-thunk so our API request and dispatching can occur within a single action creator. This will allow us to see the use cases of redux-thunk and will really clean up the code in our React container component.

API Request and Dispatching Using Thunk

First, let’s install redux-thunk:

npm install --save redux-thunk

Just like our logger, this will be applied as middleware in the creation of our store which occurs in index.js.

Import thunk like so:

import thunk from 'redux-thunk';

Then, let’s apply it as middleware as shown below:

//initialize store
let store = createStore(
TwitchApp,
applyMiddleware(thunk, logger)
);

Note: When using thunk and logger, logger should always be applied after thunk.

Let’s test this out by having our FETCH_REQUEST action dispatched two seconds after the FetchRequest action creator is called.

Open FetchRequest.js and let’s make some changes.

First off, remove the action object so we just have the following:

function FetchRequest() {
const FETCH_REQUEST = 'FETCH_REQUEST'
}export default FetchRequest

Instead of an action object, we can return a function that passes in dispatch as a parameter:

function FetchRequest() {
const FETCH_REQUEST = 'FETCH_REQUEST'
return(dispatch) => {

}
}
//return (dispatch) => is the same as function (dispatch)

Note that we can use dispatch as a parameter even without any imports thanks to redux-thunk middleware.

Next, let’s dispatch our FETCH_REQUEST action object within a setTimeoutwhich will delay the dispatch by 2 seconds:

function FetchRequest() {
const FETCH_REQUEST = 'FETCH_REQUEST'
return(dispatch) => {
setTimeout(() => {
dispatch({
type: FETCH_REQUEST,
status: "loading"
})
}, 2000)
}
}

We can also store the action object as a variable:

function FetchRequest() {
const FETCH_REQUEST = 'FETCH_REQUEST'
return(dispatch) => {
const FETCH_REQUEST_OBJ = {
type: FETCH_REQUEST,
status: "loading"
}
setTimeout(() => {
dispatch(FETCH_REQUEST_OBJ)
}, 2000)
}
}

Finally, let’s log when this action creator is reached and then log when the action object finally dispatches:

function FetchRequest() {
console.log("0 seconds in");
const FETCH_REQUEST = 'FETCH_REQUEST'
return(dispatch) => {
const FETCH_REQUEST_OBJ = {
type: FETCH_REQUEST,
status: "loading"
}
setTimeout(() => {
console.log("2 seconds in");
dispatch(FETCH_REQUEST_OBJ)
}, 2000)
}
}

If we check the local host, our loader should not appear until 2 seconds in:

Awesome!

Now that we have sampled redux-thunk, we can continue on our goal to move the API request and dispatching into a single action creator.

Create a new file called RequestApi.js in the actions folder.

First, we add the shell of this new action creator:

function RequestApi() {
return (dispatch) => {
}
}
export default RequestApi

Then, we can cut the axios import from Streams.js and paste it into this file:

import axios from 'axios';function RequestApi() {
return (dispatch) => {
}
}
export default RequestApi

We can also cut and paste the action file imports with updated relative paths:

import FetchRequest from './FetchRequest';
import FetchSuccess from './FetchSuccess';
import FetchFailure from './FetchFailure';

Remove the entire apiRequest() function in Streams.js and update RequestApi.js with the following:

function RequestApi() {
return (dispatch) => {
//API request
axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
.then(response => {
const streams = response.data.featured.map(function(feat) {
return feat.stream;
});
//dispatch FetchSuccess, order 2
})
.catch(e => {
//dispatch FetchFailure, order 3
});
//dispatch FetchRequest, order 1 }
}

Then, let’s remove the dispatching function in Streams.js and insert the dispatches in the RequestApi action creator:

import axios from 'axios';
import FetchRequest from './FetchRequest';
import FetchSuccess from './actions/FetchSuccess';
import FetchFailure from './actions/FetchFailure';
function RequestApi() {
return (dispatch) => {
//API request
axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
.then(response => {
const streams = response.data.featured.map(function(feat) {
return feat.stream;
});
//dispatch FetchSuccess, order 2
dispatch(FetchSuccess(streams))
})
.catch(e => {
//dispatch FetchFailure, order 3
dispatch(FetchFailure(e))
});
//dispatch FetchRequest, order 1
dispatch(FetchRequest())
}
}
export default RequestApi

The final piece is to import this action creator into Streams.js and dispatch it within the componentWillMount lifecycle hook:

//other imports above
import RequestApi from '../../actions/RequestApi';
//Provider/Container React Component
class Streams extends React.Component {
componentWillMount () {
this.props.store.subscribe(this.forceUpdate.bind(this));
this.props.store.dispatch(RequestApi());
}
//render
}

If we now refresh the local host, we see that our app is still working even though we’ve removed all of this code from our React container component and into an improved action creator:

Woot woot! We have successfully implemented thunk!

Implementing Redux Raven Middleware

Let’s showcase one more use case to answer: “Why use middleware?”

We are going to be using the raven-for-redux so we can send error reports to Sentry when there is an error when trying to dispatch an action.

Sentry is a cool tool for receiving reports of issues in an application. Raven.js is the JavaScript client for Sentry.

Sign up for a free account, create a new project, and confirm your email.

After that, go ahead and install standard Raven.js and Raven Middleware for Redux:

npm install --save raven-js
npm install --save raven-for-redux

Let’s import them both in index.js:

import Raven from "raven-js";
import createRavenMiddleware from "raven-for-redux";

We can now configure Raven.js by placing the following line right below our App class:

Raven.config('my-sentry-dsn').install()

Note: You can get this exact line of code with your DSN here.

Finally, we can apply the Raven for Redux middleware like so:

let store = createStore(
TwitchApp,
applyMiddleware( thunk, logger, createRavenMiddleware(Raven, {}) )
);

To test this out, you can misspell the FetchRequest dispatch in RequestApi.js:

dispatch(FetchRequest())

When your app refreshes, you should shortly receive an error message from Sentry via email:

Super cool!

Final Code

Available on GitHub.

Concluding Thoughts

Middleware has a lot of potential use cases which we simply don’t have the time to cover. Nevertheless, I think we have gone over enough to dip your feet implementing middleware and answer: “What is middleware and why use it?”

Chapter 6

Chapter 6 is now available.

Buy the Official Ebook

If you would like to support the author and receive a PDF, EPUB, and/or MOBI copy of the book, please purchase the official ebook.

Cheers,
Mike Mangialardi

--

--