Thunks and Redux Toolkit
Get started with thunks and Redux toolkit.
We'll cover the following
Introduction
Do you remember what “thunks” are? Here’s a quick reminder.
When you first learn Redux, you were likely taught that you must dispatch an action to make a state change and that the action has to be an object. Usually, it is in the following form:
{
type: "ACTION_TYPE",
payload: {}
}
Well, that’s not entirely true.
In the typical flow of a Redux app, the reducer is the only actor that requires the action to be an object!
This means you can dispatch whatever Javascript data type you want but must transform it into an object before it reaches the reducers.
This is the entire premise on which thunks are built.
By dispatching a “function” (aka thunk), a middleware could act on it. It could even pass it special arguments.
So, what is a “thunk”?
Generally speaking, a thunk is a function that wraps an expression to delay its evaluation.
If you choose to return a function from an action creator, we can safely call that a thunk.
// before:
function fetchTweetsAction () {
//return an action object
return {
}
}
// now:
function fetchTweetsAction () {
// return a function
return function() {
}
}
How does this help us at all?
We’re going to create a thunk to handle the fetching of tweets.
First, whenever a user clicks the search button, we will dispatch a thunk. Remember that this is just a function.
Now here comes the interesting bit. The dispatched function will be passed the dispatch
and getState
Redux store methods. This is what makes thunks powerful.
function fetchTweetsAction () {
// receives dispatch and getState arguments
return function(dispatch, getState) {
}
}
Within the thunk, we will then perform the actual data fetch and dispatch actions to denote loading and success states.
Let’s see how that works in code by creating a new thunk called fetchTweets
. We’ll do this in the finderSlice.js
file, keeping every state-related logic in one place.
export const fetchTweets = (searchValue, numberOfResults) => async (dispatch) => {
};
Note how this takes the searchValue
and numberOfResults
arguments and returns an async function that receives the store dispatch
method. That’s a thunk!
Under the hood, redux-toolkit
sets up a middleware that will intercept the dispatched function and invoke it with the store’s dispatch
and getState
methods. We haven’t used getState
here, as we don’t need it.
When it is time to dispatch this thunk, it’ll be invoked like this:
// dispatch with searchValue and numberOfResults
dispatch(fetchTweets('search value', 10))
Within the thunk, we will now make the actual data fetch and dispatch some actions while we’re at it:
// import the function that calls the Twitter API
import { findTweets } from "./findTweets";
export const fetchTweets = (searchValue, numberOfResults) => async (dispatch) => {
// loading ...
dispatch(isLoadingTweets());
// perform actual data fetch
const tweets = await findTweets(searchValue, numberOfResults);
// success
dispatch(loadingTweetsSuccess(tweets));
};
In the code block above, findTweets
refers to the actual fetch handler that we created in findTweets.js
.
But where do these actions come from? Let’s add those to the finderSlice
slice:
// before
const finderSlice = createSlice({
name: "finder",
initialState,
reducers: {
addTweet(state, payload) {
state.tweets = payload;
},
},
});
Remember that the createSlice
utility creates the reducer and action creators on our behalf. Let’s take advantage of this to create the isLoadingTweets
and loadingTweetsSuccess
action creators.
First, let’s change the initialState
of the slice to hold more information, e.g., loading state.
// before
const initialState = { tweets: [] };
// now
const initialState = { tweets: [], isLoading: false, error: null };
We’ll consider error handling later, so let’s have it in the initial state as null
.
Now, we can update the slice implementation as follows:
...
const finderSlice = createSlice({
name: "finder",
initialState,
reducers: {
// note that the payload is deconstructed from the action object
loadingTweetsSuccess(state, {payload}) {
state.tweets = payload;
state.isLoading = false;
state.error = null;
},
isLoadingTweets(state) {
state.isLoading = true;
},
},
});
Easy to reason about!
Now, we just need to pull out the action creators from the returned slice object:
// before
export const { addTweet } = finderSlice.actions;
// now
export const { isLoadingTweets, loadingTweetsSuccess } = finderSlice.actions;
If you followed the steps above, you’d have successfully created a thunk that is ready to be dispatched!
Let’s now move on to the next lesson where we perform the actual thunk dispatch, and change the UI to handle the loading state appropriately.
Get hands-on with 1400+ tech skills courses.