Redux Middleware: Thunk

Cody Dupuis
Nov 22, 2020 · 5 min read

Last week I covered setting up Redux for your React project and glossed over its use cases. Today’s article is meant to expand on that, diving into a few of the more complex features of Redux. If you haven’t read the first article and you are new to the Redux library, I’d suggest checking out the other article here.

Action Creators

Although this article is mainly regarding Thunk, I forgot to include Action Creators last week so I’m including them now as bonus.

Last week we talked about Redux actions, which are plain JavaScript objects that have two keys, a type key and a payload key. Action Creators are simply pure functions that can optionally accept arguments and return the action you want to create. Simple enough, huh?

Expanding on last week’s article, let’s check it out:

const user = { username: 'CTD', id: 1 }

The user variable is the payload we want to persist to our global state, so our action looks something like this:

{
type: 'LOGIN_USER',
payload: user
}

So how does an Action Creator factor into this? Like so:

function loginUser(user) {
return {
type: 'LOGIN_USER',
payload: user
}
}

That’s it! Want to see the comparison?

// raw action dispatching
dispatch({ type: 'LOGIN_USER', payload: user});
// using the action creator
dispatch(loginUser(user));

Admittedly, it doesn’t make much of a difference, but it will make your code look a little cleaner and slightly more sophisticated. Action Creators are usually reserved for those who tend to wear a monocle while programming.

Middleware

I’m going to assume you’re a programmer because I can’t imagine why else you would be reading this article. If that’s the case, then you’re probably familiar with middleware in general. Redux has a few different middleware libraries that allow you to interact with a dispatched action before it reaches your reducer. I’ll be covering the library that I’m the most familiar with: Thunk.

The first thing we need to cover regarding middleware is how to include it in your app. This is done through Redux’s applyMiddleware() function. Setup is pretty simple:

//index.jsimport { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/rootReducer';
const store = createStore(
rootReducer,
compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
),
)

And that’s it! The Thunk library is now integrated with Redux.

Quick Note About DevTools

If you’ve been dabbling in Redux, hopefully you’ve been using the DevTools extension for your browser as it makes your life so much easier. If you have, the store in this scenario may be different from what you’ve seen if you haven’t been using middleware. In that case, your store may look more like this:

const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION()
);

The above store is a different way of implementing the DevTools, but only works without middleware. I didn’t include this in the last article (and I probably should have, my bad) but if you want to use middleware in conjunction with the DevTools, you need Redux’s compose() function, which I used in the last article and in the example above. I figured teaching how to set up the store with middleware in mind would be more convenient, but I feel obligated to at least address this.

Now let’s move into Thunk.

Thunk

Assuming you’re using Redux as intended, you’re probably building a fairly large application that’s going to need to manage multiple resources asynchronously. I know I’m making a lot of assumptions today, but I’m also going to assume you’re familiar with async behavior, especially if you’re developing in JavaScript. So, all of that fluff out of the way, did you try to dispatch more than one action at a time?

If you did, your compiling phase probably failed and your browser window slapped you in the face with red text errors everywhere. Man, that’s frustrating. That’s why we have Thunk.

Simply put, Thunk allows you to dispatch more than one action at a time. It’s beautiful. I love Thunk. Expanding on the above example of logging in a user, what if you wanted some kind of loading indicator in your React app to show your user that the app is currently waiting on information to be returned?

The best way I like to implement this feature is to have a separate reducer that manages state based on if your app is currently loading info or not. It’s a super simple, super helpful reducer:

export default function manageLoading( 
state = {
loading: '',
},
action
) {
switch (action.type)
case 'START_LOAD':
return {
...state,
loading: true
}
case 'END_LOAD':
return {
...state,
loading: false
}
default: return state;
}
}

Of course, don’t forget to include it in your rootReducer:

// reducers/rootReducer.jsimport { combineReducers } from 'redux';
import managePosts from './managePosts';
import manageUsers from './manageUsers';
import manageLoading from './manageLoading';
const rootReducer = combineReducers({
managePosts, manageUsers, manageLoading
})
export default rootReducer;

Neat. Okay, now let’s move over to a full-fledged example of fetching a user from an API using our new reducer to indicate loading. We’ll have a few new action creators, one of which is for handling login errors. I won’t cover that one in detail as it is pretty self-explanatory. Additionally, this example will probably not work unless the API you are communicating with has JSON Promises that are structured exactly how I’m assuming it would be in the example. Basically, it’s an example, not an answer. Your mileage may vary.

function loginUser(user) {
return {
type: 'LOGIN_USER',
payload: user
}
}
function loginError(error) {
return {
type: 'LOGIN_ERROR',
payload: error
}
}
function startLoad() {
return {
type: 'START_LOAD'
}
}
function endLoad() }
return {
type: 'END_LOAD'
}
}
function getUser() {
dispatch(startLoad());
fetch('http://api-url.com')
.then(response => response.json())
.then(json => {
dispatch(loginUser(json.user))
dispatch(endLoad());
)
.catch(error => {
dispatch(loginError(error)
dispatch(endLoad());
)
}

That’s a pretty lengthy example, so let’s go through it step by step:

At the very beginning of the function, we immediately let the store know we are in loading state. At that point in the execution, your loading value in the store should be set to true.

Immediately after that we are initiating a fetch, except it’s happening asynchronously. While we are waiting for the Promise, this.props.loading should return true. It makes for helpful ternary statements to decide whether to show a loading indicator. Assuming (again) that we have a <Loading /> component that’s sole purpose is to show a loading screen, we could have a statement like:

this.props.loading ? <Loading /> : <Home />

With this logic, we are rendering the loading screen as long as the state says loading: true.

Notice that we don’t dispatch the endLoad() until we have a response, successful or not. In that case, the above statement will run again since React renders on state updates. That ternary statement will then evaluate to false, showing us the <Home /> component instead of <Loading />.

Here’s a link to the Thunk GitHub Repo if you want to check it out.

Conclusion

Hopefully after reading this article, you’re a little more familiar with Thunk and how it works. Bonus points for learning how to use action creators and a way of managing loading state! I hope you learned something today and go forward with that knowledge to become a more efficient React developer. Thanks for reading, and happy coding!

The Startup

Medium's largest active publication, followed by +753K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store