DEV Community

Cover image for Redux Toolkit - createAsyncThunk()
Rifky Alfarez
Rifky Alfarez

Posted on

Redux Toolkit - createAsyncThunk()

createAsyncThunk() is a function in the Redux Toolkit that is used to handle async operations such as API calls. This function automatically handles three main phases:

  1. Pending: When the API request is initiated.
  2. Fulfilled: When the request succeeds and data is received.
  3. Rejected: When the request fails.

This article is a continuation of the previous article which you can read here.

Create postSlice

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';

interface Post {
  id: number;
  title: string;
  body: string;
}

interface PostState {
  posts: Post[];
  loading: boolean;
  error: string | null;
}

const initialState: PostState = {
  posts: [],
  loading: false,
  error: null,
};

// Create a thunk to fetch data
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/posts'
  );

  return response.data;
});

// Create slice
const postSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchPosts.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.loading = false;
        state.posts = action.payload;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message || 'Failed to fetch posts';
      });
  },
});

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

posts/fetchPosts

'posts/fetchPosts' is the type for the action generated by createAsyncThunk.

General Format: '<sliceName>/<actionName>'

Why use this format?

  • To avoid action name conflicts in large applications.
  • This name is used by Redux DevTools for debugging.

Difference between reducers and extraReducers

reducer extraReducer
To make a regular reducer. To handle external actions such as createAsyncThunk.
Can create actions automatically. Does not create actions automatically.
Action must be defined in the slice. Action comes from outside the slice.

What is builder and addCase

builder is a special API of Redux Toolkit used in extraReducers to add action handlers in an organized manner. The main method is addCase, which is used to handle a specific action by specifying the action type and corresponding reducer function.

Update store.ts

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './slice/counterSlice';
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import postsReducer from './slice/postSlice';

const persistConfig = {
  key: 'root',
  storage,
};

const persistedCounterReducer = persistReducer(persistConfig, counterReducer);

export const store = configureStore({
  reducer: {
    counter: persistedCounterReducer,
    posts: postsReducer,
  },
});

export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Enter fullscreen mode Exit fullscreen mode

Use in component

import { useDispatch, useSelector } from 'react-redux';
import { AppDispatch, RootState } from './state/store';
import {
  decrement,
  increment,
  incrementByAmount,
} from './state/slice/counterSlice';
import { useEffect } from 'react';
import { fetchPosts } from './state/slice/postSlice';

export default function App() {
  const count = useSelector((state: RootState) => state.counter.value);
  const { posts, error, loading } = useSelector(
    (state: RootState) => state.posts
  );
  const dispatch: AppDispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchPosts());
  }, [dispatch]);

  return (
    <div className="min-h-screen w-full flex flex-col items-center justify-center gap-4 p-4">
      <div className="flex items-center gap-x-4">
        <button
          onClick={() => dispatch(decrement())}
          disabled={count === 0}
          className="bg-neutral-800 text-neutral-100 text-base px-2 py-1 rounded-md"
        >
          Decrement
        </button>
        <span className="text-neutral-900 text-base border border-neutral-800 rounded-md px-2 py-1">
          {count}
        </span>
        <button
          onClick={() => dispatch(increment())}
          className="bg-neutral-800 text-neutral-100 text-base px-2 py-1 rounded-md"
        >
          Increment
        </button>
        <button
          onClick={() => dispatch(incrementByAmount(10))}
          className="bg-neutral-800 text-neutral-100 text-base px-2 py-1 rounded-md"
        >
          Increment by Amount
        </button>
      </div>
      <div>
        {loading ? (
          <p>Loading...</p>
        ) : error ? (
          <p>Error: {error}</p>
        ) : (
          <ul className="grid grid-cols-4 gap-4">
            {posts.map((post) => {
              return (
                <li key={post.id} className="flex flex-col gap-y-1">
                  <h3 className="text-neutral-800 font-medium text-base">
                    {post.title.length > 20
                      ? post.title.slice(0, 20) + '...'
                      : post.title}
                  </h3>
                  <p className="text-neutral-700 text-sm">
                    {post.body.length > 150 ? post.body + '...' : post.body}
                  </p>
                </li>
              );
            })}
          </ul>
        )}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Result

result

If you get this error, update store.ts and add middleware.

Error

Add middleware

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './slice/counterSlice';
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import postsReducer from './slice/postSlice';

const persistConfig = {
  key: 'root',
  storage,
};

const persistedCounterReducer = persistReducer(persistConfig, counterReducer);

export const store = configureStore({
  reducer: {
    counter: persistedCounterReducer,
    posts: postsReducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST'],
      },
    }),
});

export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Enter fullscreen mode Exit fullscreen mode

Conclusion

By using createAsyncThunk() in Redux Toolkit, managing asynchronous operations like API calls becomes easier and more organized. This approach simplifies data fetching while ensuring a clean and scalable Redux state management structure.

I hope this guide helps you better understand Redux Toolkit, especially its async handling capabilities. If you have any questions or feedback, feel free to leave a comment!

GitHub Repo: https://github.com/rfkyalf/redux-toolkit-learn

Also, if youโ€™re interested, feel free to visit my Portfolio Website www.rifkyalfarez.my.id to explore more of my projects. Thank you for reading.

Top comments (0)