DEV Community

Ali Bahaari
Ali Bahaari

Posted on

What are "Thunk" and `createAsyncThunk` in Redux Toolkit?

Image description

What Is Thunk In Redux?:

Thunk is used for fetching data from a API and storing response in Redux states which shortens and increases code clean-up.

What did you use to fetch data?

First, by using useEffect hook and in componentDidMount lifecycle, you would have fetched data from an API. What about storing in Redux? You would have used useDispatch hook for storing and then using useSelector for getting the data.
OK? Now, this operations are assigned to Thunk and you don't need to crowd every components in which you use the data you've called an API.

After that, you should check the results for statuses which can be fulfilled, rejected and pending which can be done more easily by using Thunk.

And remember this is a quote from Redux Toolkit documentations:

This abstracts the standard recommended approach for handling async request lifecycles.

Thus, code is cleaner, more standard and more flexible in writing.

Example In Usage

Consider I have a slice called usersSlice.js. createAsyncThunk will be used and created as shown below. Assume we want to fetch users list from an API:

import { createAsyncThunk } from '@reduxjs/toolkit';

export const getUsers = createAsyncThunk(
  'usersSlice/getUsers',
  async () => {
    return await fetch('http://localhost:4000').
      then(res => res.json());
  }
);

const initialState = {
    status: null,
    data: []
}

const usersSlice = createSlice({
    name: 'usersSlice',
    initialState,
    extraReducers: {
      [getUsers.pending] = (state) => {
        state.status = 'Pending';
      },

      [getUsers.fulfilled] = (state, action) => {
        state.status = 'Fulfilled';
        state.data = action.payload;
      },

      [getUsers.rejected] = (state) => {
        state.status = 'Rejected';
      }
    }

export default usersSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

First you create a variable called getUsers which is assigned to createAsyncThunk (notice export keyword before declaring the variable). createAsyncThunk has 2 arguments. The first one is a string for specifying the Thunk name and the second one is a async function which will return a promise.

Then you create a slice by using createSlice. In extraReducers (notice reducers property is different) you specify 3 probable states of the promise which are pending, fulfilled and rejected. You decide what should Redux do in these 3 different states of the API.

  1. Pending means API manipulation is being continued.
  2. Fulfilled means response was got from API.
  3. Rejected means API call was failure.

After that, you declare the reducer you've created in configureStore:

import { configureStore } from '@reduxjs/toolkit';
import usersSlice from './slices/usersSlice';

export const store = configureStore({
    reducer: {
        usersSlice,
        ...
    }
});
Enter fullscreen mode Exit fullscreen mode

Then, create a component called UsersListComponent.js and then, you'll do as this:

import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { getUsers } from './store/slices/usersSlice';
...

  const dispatch = useDispatch();
  const usersData = useSelector(state => state.usersSlice.data);
  const usersDataStatus = useSelector(state => state.usersSlice.status);

  useEffect(() => {
    dispatch(getUsers());
  }, []);
Enter fullscreen mode Exit fullscreen mode

First you should dispatch the async function you've created by using createAsyncThunk. All operations will be done by Redux and BOOM! Everything is ready and you can use useSelector hook to get data and use it as you like:

return (
  <>
    {
      usersData.map(userData => (
        <div>
          <span>{userData.id}</span>
          <span>{userData.firstName}</span>
          <span>{userData.lastName}</span>
        </div>
      ))
    }
  </>
);
Enter fullscreen mode Exit fullscreen mode

Also you can use status state for checking the status of the API:

return (
  <>
    {
      usersDataStatus === 'Pending' ? <span>Pending</span> :
      usersDataStatus === 'Fulfilled' ? <span>Fulfilled</span> :
      usersDataStatus === 'Rejected' ? <span>Rejected</span> : 
      ''
    }

    {
      usersData.map(userData => (
        <div>
          <span>{userData.id}</span>
          <span>{userData.firstName}</span>
          <span>{userData.lastName}</span>
        </div>
      ))
    }
  </>
);
Enter fullscreen mode Exit fullscreen mode

Everything works as before but cleaner, more standard and more flexible.
Congratulation! You've been learning how to use Thunk and createAsyncThunk in Redux Toolkit.

Top comments (5)

Collapse
 
phryneas profile image
Lenz Weber

Please note that we do not recommend the object notation of extraReducers any more and will deprecate & remove it in the future. Please use the builder notation as shown in redux-toolkit.js.org/api/createSli...

Collapse
 
alibahaari profile image
Ali Bahaari

Thank you so much for your attention. Actually I was confused which one should be explained. In the future I'll explain more about builder notation in the future.
Thanks again!

Collapse
 
amir2mi profile image
Amir M. Mohamadi

Thanks for informing.
Redux toolkit is awesome, but the new syntax is just verbose, these kinds of changes just ruin developer experience and increase complexity.

Collapse
 
phryneas profile image
Lenz Weber

The new syntax is necessary to support these features with the TypeScript declarations. Even if you are using JavaScript, that means you will get autocomplete in your editor that you could not have before - since the editor will be using TypeScript under the hood to give you that autocomplete.

So these changes are there to give you better developer experience.

Thread Thread
 
amir2mi profile image
Amir M. Mohamadi • Edited

I didn't know about it, that would be a big help!
My biggest issue with Redux and its related tools is this kind of changes, so you have to learn both ways to make sure you can handle a simple task.