What the heck is a 'thunk'?

Thunk! (the redux kind)

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 not_a_thunk() {
  // this one is a "thunk" because it defers work for later:
  return function() {
    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 not_a_thunk()() – 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. They are required to only be objects, and they must have a type property. Other than that, they can contain whatever you want – anything you need to describe the action you want to perform.

Actions look like this:

{
  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'
  };
}

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. Boring 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? Because reducers are supposed to be “pure” (as in, they don’t change anything) we can’t do any of that inside a reducer.

If you wanted an action to do something, that code would need to live inside a function.

It would be nice if an action creator could return that function 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.

Set up redux-thunk in your project

If you have a project that already has Redux set up, adding redux-thunk is only a few 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)
);

Learning React can be a struggle -- so many libraries and tools!
My advice? Ignore all of them :)
For a step-by-step approach, read my book Pure React.

Loved it! Very well written and put together. Love that you focused only on React. Wish I had stumbled onto your book first before I messed around with all those scary "boilerplate" projects.
— John Lyon-Smith
comments powered by Disqus