A deeper understanding of Thunk

Posted by Jamie M. Brown on February 11, 2019

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters. - NPM

The work thunk is a generic term used to describe a function that wraps an expression to delay its evaluation.

Example:

let y = 5 * 6

//the above expression can be wrapped in a function to become a thunk:
let bar = () => 5 * 6;

In Redux, actions are just objects that include a “type” key and whatever other data you need for that action. Action creators are functions that return action objects. They add a layer of abstraction and makesactions less error prone by allowing you to use one action creator evertime you want a specific action. So instead of writing an action, you call a function that returns the action object for you.

function actionCreator(){
     return {
          type: ‘MY_ACTION’,
          payload: ‘action info’
    }
}

The synchronous and pure flow of data through Redux’s components is well-defined with distinct, simple roles. Action creators create objects → objects are dispatched to the store → the store invokes reducers → reducers generate new state → listeners are notified of state updates. - Gabriel Lebec

We use Thunk inside action creators so that our reducers remain pure functions even when the action creator is asynchronous (i.e. anything impure will be wrapped in a thunk). The action creator is returning a function (which is pure as a reutrn value). Note that the invocation of that function may not be pure (like in the case of an asynchronous call to an API), but the function itself as a return value is. In addition to making the return value of the action creator pure, Thunk looks at every action called and if it returns a function, it calls that function (so that it doesn’t have to be invoked manually).

Example:

export function fetchActivities() {
    return (dispatch) => {
       dispatch({type: 'LOADING_DATA'});
       return fetch('/api/activities', {
            headers:{
            'Content-Type': 'application/json',
            'Accept': 'application/json'
            }
        })
        .then(checkStatus)
        .then(activities => dispatch({type: 'FETCH_ACTIVITIES', payload: activities}))
        .catch(err => {err.json().then(message  => dispatch({type: 'ACTIVITY_ERROR', payload: message}))})
    }
}

To set up Thunk, you need to first install it:

npm install --save redux-thunk

Then you need to import it and apply it to your reducer using applyMiddleware (which also needs to be imported). This ensures that whenever a value gets dispatched to the redux Store, it first passes through the provided middleware (in this case, Thunk):

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
 
const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

Note that if you need to add multiple middlewares (like thunk AND dev tools) to your store, you need to also use compose.

import thunk from 'redux-thunk';
import {createStore, applyMiddleware, compose} from 'redux';
import appReducer from './reducers/appReducer.js';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// create store using appReducer and Thunk
const store = createStore(appReducer, composeEnhancers(applyMiddleware(thunk)));

Resources