Q: What is a ‘thunk’?
A: The sound your head makes when you first hear about redux-thunk
.
Ok sorry, that was awful.
But seriously: Redux Thunk is a really confusing thing when you first hear about it. I think it’s mostly because of that word “thunk.” So let’s clear that up first.
thunk, n.
A thunk is another word for a function. But it’s not just any old function. It’s a special (and uncommon) name for a function that’s returned by another. Like this:
function wrapper_function() {
// this one is a "thunk" because it defers work for later:
return function thunk() { // it can be named, or anonymous
console.log('do stuff now');
};
}
You already know this pattern. You just don’t call it “thunk.” If you want to execute the “do stuff now” part, you have to call it like wrapper_function()()
– calling it twice, basically.
redux-thunk
So how does this apply to Redux?
Well, if you’re familiar with Redux, you’ll know that it’s got a few main concepts: there are “actions”, “action creators”, “reducers”, and “middleware.”
Actions are just objects. As far as Redux is concerned, out of the box actions must be plain objects, and they must have a type
property. Aside from that, they can contain whatever you want – anything you need to describe the action you want to perform.
Actions look like this:
// 1. plain object
// 2. has a type
// 3. whatever else you want
{
type: "USER_LOGGED_IN",
username: "dave"
}
And, since it’s kind of annoying to write those objects by hand all the time (not to mention error-prone), Redux has the concept of “action creators” to stamp these things out:
function userLoggedIn() {
return {
type: 'USER_LOGGED_IN',
username: 'dave'
};
}
It’s the same exact action, but now you can “create” it by calling the userLoggedIn
function. This just adds one layer of abstraction.
Instead of writing the action object yourself, you call the function, which returns the object. If you need to dispatch the same action in multiple places around your app, writing action creators will make your job easier.
Actions are Boring
Isn’t it kind of funny that Redux’s so-called “actions” don’t actually do anything? They’re just objects. Plain and simple and inert.
Wouldn’t it be cool if you could actually make them do something? Like, say, make an API call, or trigger other actions?
Since reducers are supposed to be “pure” (as in, they don’t change anything outside their scope) we can’t do any API calls or dispatch actions from inside a reducer.
If you want an action to do something, that code needs to live inside a function. That function (the “thunk”) is a bundle of work to be done.
It would be nice if an action creator could return that function – the bundle of work – instead of an action object. Something like this:
function getUser() {
return function() {
return axios.get('/current_user');
};
}
If only there were some way to teach Redux how to deal with functions as actions…
Well, this is exactly what redux-thunk does: it is a middleware that looks at every action that passes through the system, and if it’s a function, it calls that function. That’s all it does.
The only thing I left out of that little code snippet is that Redux will pass two arguments to thunk functions: dispatch
, so that they can dispatch new actions if they need to; and getState
, so they can access the current state. So you can do things like this:
function logOutUser() {
return function(dispatch, getState) {
return axios.post('/logout').then(function() {
// pretend we declared an action creator
// called 'userLoggedOut', and now we can dispatch it
dispatch(userLoggedOut());
});
};
}
Update: As rixman mentions in the comments, the getState
function can be useful for deciding whether to fetch new data, or return a cached result, depending on the current state.
That’s about it. That’s what redux-thunk
is for.
A Very Small Library Indeed
The entirety of the redux-thunk library is this code right here:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
// This gets called for every action you dispatch.
// If it's a function, call it.
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// Otherwise, just continue processing this action as usual
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
After you install redux-thunk in your project, and apply the middleware, every action you dispatch will pass through this bit of code. It calls actions that are functions (and returns whatever they return), and otherwise passes the action along to then next middleware, or to Redux itself (which is what next(action)
does).
Set up redux-thunk in your project
If you have a project that already has Redux set up, adding redux-thunk is two steps.
First, install the package:
npm install --save redux-thunk
Then, wherever you have your Redux setup code, you need to import redux-thunk
and insert its middleware into Redux:
// You probably already import createStore from 'redux'
// You'll need to also import applyMiddleware
import { createStore, applyMiddleware } from 'redux';
// Import the `thunk` middleware
import thunk from 'redux-thunk';
// Import your existing root reducer here.
// Change this path to fit your setup!
import rootReducer from './reducers/index';
// The last argument to createStore is the "store enhancer".
// Here we use applyMiddleware to create that based on
// the thunk middleware.
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Just make sure you wrap thunk
in the applyMiddleware
call, or it won’t work.
After this, you’re all set: you can now dispatch functions that do whatever you need.
And now that you have redux-thunk set up, you can learn how to fetch data with Redux.