Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Apple iOS & macOSApple arbeitet an einem Fitness-Tracker ohne Display(22.09.2026 um 21:33 Uhr)
Apple iOS & macOSApple stoppt Entwicklung des AirTag-großen KI-Pins(22.09.2026 um 22:22 Uhr)
Apple iOS & macOSApple arbeitet an einem Fitness-Tracker ohne Display(22.09.2026 um 21:33 Uhr)
Apple iOS & macOSApple stoppt Entwicklung des AirTag-großen KI-Pins(22.09.2026 um 22:22 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Mastering Async Logic in Redux Toolkit with createAsyncThunk

Introduction Modern JavaScript applications need to handle asynchronous logic — fetching data, posting forms, authentication, file uploads, etc. In older Redux projects, async operations required complex setups using middlewares like r…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




Introduction



Modern JavaScript applications need to handle asynchronous logic — fetching data, posting forms, authentication, file uploads, etc. In older Redux projects, async operations required complex setups using middlewares like redux-thunk, action creators, constants, and reducers. This made Redux feel boilerplate-heavy and repetitive.

Redux Toolkit (RTK) changed everything. It streamlined Redux development and made async operations simpler, readable, and scalable with the createAsyncThunk() API. But what is createAsyncThunk()? Let’s answer that question in the next section.





What is create createAsyncThunk()



createAsyncThunk is a utility from Redux Toolkit designed to simplify writing async logic like API calls. It automatically:

Manages pending, fulfilled, and rejected actions

Handles errors for you

Supports async/await cleanly

Let reducers respond to async life cycles easily

Think of it as a smart wrapper for asynchronous requests in Redux.





Basic structure of createAsyncThunk





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

const postUrl = "https://jsonplaceholder.typicode.com/posts";

export const fetchPosts = createAsyncThunk("posts/fetchPosts", async () => {
const response = await fetch(postUrl);
if (!response.ok) throw Error("An error occoured! Unable to fetch posts.");
const data = await response.json();
return data;
});





From the code above, we see that createAsyncThunk takes two arguments. The first argument is used as a prefix for the automatically generated action types, and the code above automatically generates three Redux actions:

posts/fetchPosts/pending, triggered when the request starts

posts/fetchPosts/fulfilled, triggered when the request is successful

posts/fetchPosts/rejected, triggered when the request fails

The second argument is a payload creator callback and createAsyncThunk function above either returns a promise that contains some data or a rejected promise with an error. And as we see from the payload creator callback, we are fetching data from the jsonplaceholder website.

In Redux Toolkit, action creators are automatically generated based on the specified reducer in the createSlice function. However, sometimes, a slice reducer needs to respond to other actions that are not defined as part of the slice reducer. And this is the case for actions generated from createAsyncThunk.

Actions generated from createAsyncThunk are handled by extraReducers, which uses the builder callback notation as seen below:




  extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state, action) => {
state.status = "loading";
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = "success";
let min = 1;
const loadedPosts = action.payload?.map((post) => {
post.date = sub(new Date(), { minutes: min++ }).toISOString();
return post;
});
state.posts = state.posts.concat(loadedPosts);
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = "failed";
state.error = action.error?.message;
})
},
});






In Redux Toolkit (RTK), the builder callback notation is a pattern used inside createSlice() to define reducers for actions created by createAsyncThunk() or other external actions. Instead of defining reducers as an object, you use a function that receives a builder object parameter—allowing you to chain method calls to handle different action types more flexibly, as seen in the code above.

The builder parameter of the extraReducers function is an object that lets you define additional case reducers that run in response to the actions defined outside of the slice, as seen in the code above.

In this article, we will learn how to handle asynchronous logic in Redux Toolkit using createAsyncThunk by building a simple blog. But before we delve into this, the prerequisites to gain the most from this article are given below.





Prerequistes




  1. NodeJS should be installed on your system

  2. Knowledge of React

  3. Knowledge of JavaScript

  4. Knowledge of Redux Toolkit
    ## Building A Blog With React And Redux Toolkit
    Redux does everything synchronously, so anything asynchronous has to happen outside the store, and this is where redux middleware comes in. The most common Redux middleware is Redux Thunk. The word thunk is a programming term that means a piece of code that does some delayed work. And Redux thunk is recommended as the standard approach for writing async logic with Redux.



Redux Toolkit uses Redux thunk under the hood to handle asynchronous actions, and it is included by default in Redux Toolkit's configureStore.



In this section, we will learn how Redux handles asynchronous logic by building a blog with React and Redux Toolkit. Let’s get started in the subsection below:





Installation



We will work with Vite in this article. Vite is a super-fast build tool that replaces Create React App (CRA) for modern React development. To use it, in your terminal, run the code below:




npm create vite@latest counter-app






You’ll be prompted with something like this:




? Select a framework: › - Use arrow-keys. Return to submit.
Vanilla
❯ React
Vue
Svelte
Others






Choose React.

Now go into the app folder by running:




cd counter-app






Then run the app with:




npm run dev






After running the code above, to view the app, visit http://localhost:5173/, and you will get:



React and Vite logo



Next, install Redux Toolkit and the react-redux and date-fns package by running:




npm install @reduxjs/toolkit react-redux date-fns






Now we can set up Redux Toolkit inside the app. To do this, first, we configure the Redux store by following the steps in the next section.






Configure the Store



To configure the Redux store, create an app directory inside the src directory. Then create a file named store.js inside the app directory. Now add the code below to the store.js file:




// app/store.js

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";

export const store = configureStore({
reducer: { },
});






After configuring the store, we need to provide the global state to our application.






Provide the Store to React



To provide the store to React, replace the code in the main.jsx with the following code:




import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
import { store } from "./app/store.js";
import { Provider } from "react-redux";

createRoot(document.getElementById("root")).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);






In the code above, we used the Provider component to provide our store — our global state, to our application. Next, we create a slice.






Create Slice



To create the postSlice, create a folder called features inside the src directory, and inside the features directory, create a folder named posts. Inside the posts directory, create a file named postSlice.js and add the code below to the postSlice.js:




import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { sub } from "date-fns";

const postUrl = "https://jsonplaceholder.typicode.com/posts";

export const fetchPosts = createAsyncThunk("posts/fetchPosts", async () => {
const response = await fetch(postUrl);
if (!response.ok) throw Error("An error occoured! Unable to fetch posts.");
const data = await response.json();
return data;
});

export const addPost = createAsyncThunk("post/addPost", async (postBody) => {
const response = await fetch(postUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postBody),
});
const data = await response.json();

return data;
});


const initialState = {
posts: [],
status: "idle", // pending | success | failed
error: null,
};

export const postSlice = createSlice({
name: "posts",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchPosts.pending, (state, action) => {
state.status = "loading";
})
.addCase(fetchPosts.fulfilled, (state, action) => {
state.status = "success";
let min = 1;
const loadedPosts = action.payload?.map((post) => {
post.date = sub(new Date(), { minutes: min++ }).toISOString();
return post;
});
state.posts = state.posts.concat(loadedPosts);
})
.addCase(fetchPosts.rejected, (state, action) => {
state.status = "failed";
state.error = action.error?.message;
})
.addCase(addPost.fulfilled, (state, action) => {
state.status = "success";
action.payload.date = new Date().toISOString();
state.posts.push(action.payload);
})
},
});

// get the initial state object in this slice
// this function is passed to the useSelector hook to get data from the initial state in this slice in a component.
export const selectPostSliceState = (state) => state.posts;

export const {} = postSlice.actions;

export default postSlice.reducer;






In the code above, the initialState is an object with 3 properties namely posts — which is an empty array because we have not hydrated it yet, status — which has a value of idle but the value can also be loading, succeeded or failed, and error — which have a value of null.



The selectPostSliceState function gives access to the initialState when it is used with the useSelector hook from a component, as seen below:

const { posts, status, error } = useSelector(selectPostSliceState)

When the code above is used inside a component, it destructures the posts, status, and error from the initialState.



The benefit of using this pattern instead of accessing the initialState from our components like this:




const { posts, status, error } = useSelector( (state) => state.posts)






is that if the shape of the initialState changes, we do not need to change the code in all the components where it is used; we only need to change the selectPostSliceState in the slice.



Next, we need to add the created reducer to the store. To do this, update the store.js file as seen in the code below:




import { configureStore } from "@reduxjs/toolkit";
import postReducer from "../features/posts/postSlice";

export const store = configureStore({
reducer: {
posts: postReducer,
},
});









Create Post List



Now, we create the postList component. To do this, in the posts directory inside the features directory, create a file named postList.jsx and add the code below to it:




import { useSelector } from "react-redux";
import { selectPostSliceState } from "./postSlice";
import PostItem from "./PostItem";

const PostList = () => {
const { posts, status, error } = useSelector(selectPostSliceState);

const sortedPosts = posts
.slice()
.sort((a, b) => b.date.localeCompare(a.date));

return (
<div>
{status === "loading" && <h1>Loading...</h1>}
{status === "failed" && <p>{error}</p>}
{status === "success" &&
sortedPosts.map((post) => <PostItem post={post} key={post.id} />)}
</div>
);
};

export default PostList;






The code above displays loading if status is loading, it displays the error if status is failed, and maps through the sortedPosts array passing each post to the PostItem component if status is success.

Next, let’s create the postItem component. To do this, create a file named postItem.jsx inside the posts directory that is in the features directory. Then add the code below to the postItem.jsx component:




import TimeAgo from "./TimeAgo";

const PostItem = ({ post }) => {

return (
<article>
<div>
<h2>{post.title}</h2>
<TimeAgo timestamp={post.date} />
</div>
</article>
);
};

export default PostItem;






Now, let's create the TimeAgo component. In the posts directory that is inside the features directory, create a component named TimeAgo.jsx and the following code to this component:




import { parseISO, formatDistanceToNow } from "date-fns";

const TimeAgo = ({ timestamp }) => {
let timeAgo = "";
if (timestamp) {
const date = parseISO(timestamp);
const timePeriod = formatDistanceToNow(date);
timeAgo = `${timePeriod} ago`;
}

return (
<span title={timestamp}>
&nbsp; <i>{timeAgo}</i>
</span>
);
};

export default TimeAgo;






The code above displays the time in human-readable format using the date-fns package.






Create The AddPost Form



To do this, in the posts directory, inside the features directory, create a file named AddPost.jsx and add the following code to it:




import { useState } from "react";
import { useDispatch } from "react-redux";
import { addPost } from "./postSlice";

const AddPost = () => {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const dispatch = useDispatch();

const onSavePostClicked = () => {
try {
const newPost = { title, body };
dispatch(addPost(newPost)).unwrap();
} catch (error) {
console.log("error", error.message);
}

setTitle("");
setBody("");
};

return (
<section>
<h2>Add a New Post</h2>
<form>
<label htmlFor="postTitle">Post Title:</label>
<input
id="postTitle"
name="postTitle"
onChange={(e) => setTitle(e.target.value)}
value={title}
type="text"
/>
<label htmlFor="postContent">Content:</label>
<textarea
id="postContent"
name="postContent"
value={body}
onChange={(e) => setBody(e.target.value)}
/>
<button type="button" onClick={onSavePostClicked}>
Submit
</button>
</form>
</section>
);
};

export default AddPost;






From the code above, we see that the AddPost component is a form with two controlled components, namely input and textarea.

The onSavePostClicked dispatch a thunk from the AddPost component (using dispatch(addPost(newPost)).unwrap()). This returns a promise — but by default, that promise does not reject on error. Instead, it always resolves to an “action object” describing what happened.

And that’s where .unwrap() comes in. The unwrap() method converts that action result back into a “normal” promise that behaves like a regular async/await call.



Now update the App.jsx file by replacing the code with the following:




import PostList from "./features/posts/PostList";
import AddPostForm from "./features/posts/AddPost";

function App() {
return (
<main className="App">
<AddPostForm />
<PostList />
</main>
);
}

export default App;






Finally, replace the code in the index.css file with the following:




* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

html {
font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif;
background: #333;
color: whitesmoke;
}

body {
min-height: 100vh;
font-size: 1.5rem;
padding: 0 10% 10%;
}

input,
textarea,
button,
select {
font: inherit;
margin-bottom: 1em;
}

main {
max-width: 500px;
margin: auto;
}

section {
margin-top: 1em;
}

article {
margin: 0.5em 0.5em 0.5em 0;
border: 1px solid whitesmoke;
border-radius: 10px;
padding: 1em;
}

h1 {
font-size: 3.5rem;
}

p {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.4;
font-size: 1.2rem;
margin: 0.5em 0;
}

form {
margin-top: 10px;
display: flex;
flex-direction: column;
}

.postCredit {
font-size: 1rem;
}






Now, when we view our app, we get:



Add post form without post list



When we view the app, we only see the add post form. The posts from jsonplaceholder are not visible. This is not the kind of behaviour we want from our app. We want to load all posts once our app loads and display them. To achieve this, we call fetchPosts by using the code below:




 store.dispatch(fetchPosts());






Now, update the code in the main.jsx file with the following:




import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
import { store } from "./app/store.js";
import { Provider } from "react-redux";
import { fetchPosts } from "./features/posts/postSlice.js";

// call fetchPosts
store.dispatch(fetchPosts());

createRoot(document.getElementById("root")).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>
);






Now, we get:



Add post form and post list






Conclusion



createAsyncThunk is one of the most powerful features of Redux Toolkit. It:

Removes async boilerplate

Simplifies API calls

Improves error handling

Works perfectly with modern React apps

Produces clean, scalable state management code



If you used to hate Redux for being "too heavy", Redux Toolkit — especially createAsyncThunk — will change your mind.

Lastly, I do hope that you have learned something from this article. Kindly leave your thoughts in the comment section below.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Mastering Async Logic in Redux Toolkit with createAsyncThunk

Thematisch verwandte Begriffe: Mastering, Async, Logic, Redux · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-82000 | Adobe Experience Manager Forms JEE is affected by a Server-Side Request …
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick