The source code for this portfolio is available on GitHub.

Async Actions with Redux-Thunk

Stefan KudlaStefan Kudla | |
Cover image for Async Actions with Redux-Thunk

Async Actions with Middleware and Thunks

Redux Docs

Thunks

A thunk is a function used to delay an action until it's needed by the app.

Fun Fact: thunk comes from a play on the word “think” but in the past tense.

In JavaScript, functions are thunks! They hold a computation and they can be executed at any time and/or passed along to other functions, whom if they choose to, can be executed at any time. A common practice is for thunks to be returned by a higher order function. The returned thunk contains the process that is to be delayed until needed. For Example:

const orderPizza = () => console.info('Send Order')
orderPizza();
const getOrderStatusThunk = () => {
  return () => {
    console.info('Getting Status');
  }
const getFinalStatus = getOrderStatusThunk();
getFinalStatus();

Thunks in Redux

Why use thunks?

A bit of data for comparison: reduxthunk-vs-saga-vs-observable.png


In Redux, thunks can be used to hold asynchronous logic that interacts with the store. When thunks are dispatched to the store, their enclosed asynchronous computations are evaluated first, then sent off to the store.

The arguments passed into thunks are dispatch and getState, both coming from the store itself. This allows actions to be dispatched or for the state to be referenced within it's contained logic. They help in moving complex logic out of components or hooks along with interacting with any Redux store.


Middleware In Redux

Redux middleware extends the store’s abilities and lets you write asynchronous logic that can interact with the store. Middleware is added to the store either through createStore() or configureStore(). The redux-thunk package is a popular tool when using middleware in a Redux application.

Redux Thunk Middleware

The redux-thunk middleware package 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.

thunk middleware:

// standard middleware definition, with 3 nested functions:
// 1) Accepts `{dispatch, getState}`
// 2) Accepts `next`
// 3) Accepts `action`
const thunkMiddleware =
  ({ dispatch, getState }) =>
  next =>
  action => {
    // If the "action" is actually a function instead...
    if (typeof action === 'function') {
      // then call the function and pass `dispatch` and `getState` as arguments
      return action(dispatch, getState)
    }
    // Otherwise, it's a normal action - send it onwards
    return next(action)
  }

action creators:

import { fetchTodos } from '../actions';
const fetchTodosThunk = (
  dispatch, 
  getState
) => {
  setTimeout(
    dispatch(fetchTodos()), 
    5000);
};
/*
redux-thunk allows the returned 
thunk to be dispatched
*/
store.dispatch(fetchTodosLater());

Thunk middleware pairs perfectly with Redux since it's not making Redux do something it doesn't want to. Thunks help repeate the idea of currying. In fact, the redux-thunk package has been included in the Redux-Toolkit.

Currying is breaking down a function that takes multiple arguments into a series of functions that each take only one argument.

Creating an Async Thunk

import { createAsyncThunk } from '@reduxjs/toolkit';
import { userAPI } from './userAPI';
const fetchUser = createAsyncThunk(
  'users/fetchByIdStatus',
  async (user, thunkAPI) => {
    const response = await userAPI.fetchById(user.id);
    return response.data;
  }
);
const user = {username: "coder123", id: 3};
store.dispatch(fetchUser(user))

Creating a Slice of the Store

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { client } from '../api';
const initialState = {
  pizzas: [],
  status: 'idle'
};
export const fetchPizzas = createAsyncThunk('pizzas/fetchPizzas', async () => {
  const { pizzas, err } = await client.get('/api/v2/pizzas');
  if (err) {
    throw err;
  };
  return pizzas
});
const pizzasSlice = createSlice({
  name: 'pizzas',
  initialState,
  reducers: {
    addPizza: (state, action) => {
      state.pizzas.push(action.payload);
    }
  },
  extraReducers: {
    [fetchPizzas.pending]: (state, action) => {
      state.status = 'in preperation';
    },
    [fetchPizzas.fulfilled]: (state, action) => {
      state.status = 'delivered';
      state.pizzas = state.pizzas.concat(action.payload);
    }
  }
}); 

Thats it for now :-)