A thunk is a function used to delay a computation until it is needed by an application. The term thunk comes from a play on the word “think” but in the past tense.
In JavaScript, functions are thunks since they hold a computation and they can be executed at any time or passed to another function to 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.
const alarmOne = () => {console.log("Wake Up!!!");};alarmOne(); // "Wake Up!!!"const getAlarmThunk = () => {return () => {console.log("Wake Up!!!");}};const alarmTwo = getAlarmThunk();alarmTwo(); // "Wake Up!!!"
In Redux thunks can be used to hold asynchronous logic that interacts with the Redux store. When thunks are dispatched to the store the enclosed asynchronous computations are evaluated before making it to the Redux store. The arguments passed to thunks are the Redux store methods dispatch
and getState
. This allows actions to be dispatched or for the state to be referenced within the containing logic.
Other benefits of thunks are:
import { fetchTodos } from '../actions';const fetchTodosThunk = (dispatch,getState) => {setTimeout(dispatch(fetchTodos()),5000);};
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.
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.
import { fetchTodos } from '../actions';const fetchTodosLater = () => {return (dispatch, getState) => {setTimeout(dispatch(fetchTodos()),5000);}};/*redux-thunk allows the returnedthunk to be dispatched*/store.dispatch(fetchTodosLater());
redux-thunk
PackageThe redux-thunk
package is included in the Redux Toolkit (@reduxjs/redux-toolkit
) package. To install @reduxjs/redux-toolkit
or the standalone redux-thunk
package use npm
.
The redux-thunk
middleware allows for asynchronous logic when interacting with the Redux store.
npm install @reduxjs/redux-toolkitnpm install redux-thunk
createAsyncThunk()
accepts a Redux action type string and a callback function that should return a promise. It generates promise lifecycle action types based on the action type prefix that you pass in, and returns a thunk action creator that will run the promise callback and dispatch the lifecycle actions based on the returned promise.
The callback function takes a user-defined data argument and a thunkAPI object argument. The data argument is originally sent as an argument to the thunk action creator where an object can be used if multiple points of data are necessary. The thunkAPI object contains the usual thunk arguments such as dispatch and getState.
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))
extraReducers
PropertyThe object passed to createSlice()
may contain a fourth property, extraReducers
, which allows createSlice()
to respond to other action types besides the types it has generated. This is useful when handling asynchronous logic using thunks.
The logic within extraReducers
that acts on the slice of state
can safely use mutatable updates because it uses Immer internally.
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';import { client } from '../api';const initialState = {todos: [],status: 'idle'};export const fetchTodos = createAsyncThunk('todos/fetchTodos', async () => {const response = await client.get('/todosApi/todos');return response.todos;});const todosSlice = createSlice({name: 'todos',initialState,reducers: {addTodo: (state, action) => {state.todos.push(action.payload);}},extraReducers: {[fetchTodos.pending]: (state, action) => {state.status = 'loading';},[fetchTodos.fulfilled]: (state, action) => {state.status = 'succeeded';state.todos = state.todos.concat(action.payload);}}});