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:
- Pending: When the API request is initiated.
- Fulfilled: When the request succeeds and data is received.
- 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;
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;
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>
);
}
Result
If you get this error, update store.ts and add middleware.
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;
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)