1. Introduction to Async Logic in Redux Toolkit
Handling asynchronous logic in Redux often involves a lot of boilerplate code, such as creating action types, action creators, and reducers to handle different states (loading, success, error). Redux Toolkit simplifies this with createAsyncThunk, which allows you to define a "thunk" for asynchronous operations with minimal setup.
createAsyncThunk:
- Automatically generates pending, fulfilled, and rejected action types.
- Makes it easier to handle side effects like API requests.
- Integrates seamlessly with slices created using createSlice.
2. Using createAsyncThunk for API Calls
Let's walk through creating an async thunk to fetch data from a public API and manage different loading states.
Step 1: Setting Up a Simple API Service
We'll use a free public API to demonstrate this example. Let's assume we have an API endpoint that returns a list of posts.
Step 2: Creating an Async Thunk
First, create a new slice file named postsSlice.js inside the features/posts directory. We'll use createAsyncThunk to fetch posts asynchronously.
// src/features/posts/postsSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// Async thunk to fetch posts from an API
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
return data; // This will be the 'fulfilled' action payload
});
const postsSlice = createSlice({
name: 'posts',
initialState: {
posts: [],
status: 'idle', // idle | loading | succeeded | failed
error: null,
},
reducers: {
// Optional: add reducers for synchronous actions
},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = 'succeeded';
state.posts = action.payload;
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
},
});
export default postsSlice.reducer;
Explanation:
createAsyncThunk: This function takes two arguments: a string action type and an asynchronous function. The async function is where the API call happens. When the promise resolves, the data is returned and automatically dispatched as the fulfilled action payload.
extraReducers: This is used to handle actions generated by createAsyncThunk. We manage three states: pending, fulfilled, and rejected.
3. Integrating Thunks into Components
Now, let's use the fetchPosts thunk in a React component and display the data.
Step 1: Create a PostsList Component
Create a PostsList.js component in the features/posts directory:
// src/features/posts/PostsList.js
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchPosts } from './postsSlice';
const PostsList = () => {
const dispatch = useDispatch();
const posts = useSelector((state) => state.posts.posts);
const status = useSelector((state) => state.posts.status);
const error = useSelector((state) => state.posts.error);
useEffect(() => {
if (status === 'idle') {
dispatch(fetchPosts());
}
}, [status, dispatch]);
let content;
if (status === 'loading') {
content = <p>Loading...</p>;
} else if (status === 'succeeded') {
content = (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
} else if (status === 'failed') {
content = <p>{error}</p>;
}
return (
<section>
<h2>Posts</h2>
{content}
</section>
);
};
export default PostsList;
Explanation:
The useEffect hook dispatches fetchPosts when the component mounts, but only if the current status is 'idle'.
The status is checked to determine which content to render (loading spinner, list of posts, or error message).
Step 2: Add PostsList to the App
Update the main App.js file to include the PostsList component:
// src/App.js
import React from 'react';
import PostsList from './features/posts/PostsList';
function App() {
return (
<div className="App">
<PostsList />
</div>
);
}
export default App;
4. Best Practices for Async Thunks
Avoid Heavy Logic in Components: Keep components clean by dispatching thunks to handle asynchronous logic.
Centralize Error Handling: Handle errors in your slice rather than repeating logic in each component.
Normalize Data: Consider normalizing state shape using libraries like normalizr to manage complex data structures efficiently.
Memoize Selectors: Use createSelector from reselect to memoize selectors for better performance.
5. Conclusion and Next Steps
In this part, we explored how to handle asynchronous logic in Redux Toolkit using createAsyncThunk. We learned how to create an async thunk, handle different states, and use it in a component. In the next part, we'll dive into RTK Query—a powerful tool for data fetching and caching that further simplifies Redux development.
_
Stay tuned for Part 3: Introduction to RTK Query!_
Top comments (0)