شرح React createAsyncThunk لجلب البيانات من API وعرض حالات التحميل والنجاح والفشل
عند بناء تطبيقات React الحقيقية، غالبًا ستحتاج إلى جلب البيانات من API خارجي، مثل عرض المنتجات أو المستخدمين أو الطلبات أو المقالات. وهنا تظهر أهمية React createAsyncThunk داخل Redux Toolkit، لأنها تساعدك على تنظيم عمليات جلب البيانات بطريقة واضحة، مع التعامل مع حالات الانتظار والنجاح والفشل بسهولة.
بدلًا من كتابة أكواد كثيرة لإدارة حالة التحميل والخطأ والبيانات، توفر لنا Redux Toolkit دالة createAsyncThunk التي تنشئ بشكل تلقائي ثلاث حالات مهمة لأي طلب API وهي: pending و fulfilled و rejected. وهذا يجعل الكود أكثر ترتيبًا وأسهل في الصيانة.
في هذا المقال سنشرح بالتفصيل كيفية استخدام React createAsyncThunk مع API، وكيفية انتظار البيانات، وعرض رسالة تحميل أثناء الطلب، ثم عرض البيانات عند النجاح، أو رسالة خطأ عند الفشل.
ما هي createAsyncThunk؟
حسب التوثيق الرسمي، تستقبل createAsyncThunk ثلاثة أجزاء أساسية: اسم الـ action، ودالة async تُرجع Promise، وخيارات إضافية اختيارية. كما تقوم بإنشاء حالات lifecycle تلقائيًا للطلب: pending عند بداية الطلب، fulfilled عند نجاحه، و rejected عند فشله.
هذا يجعلها مناسبة جدًا لجلب البيانات من API داخل تطبيقات React بطريقة منظمة.
ما الحالات التي تنشئها createAsyncThunk؟
pending
تحدث عند بداية طلب البيانات. نستخدمها غالبًا لتغيير قيمة loading إلى true.
fulfilled
تحدث عندما ينجح الطلب وترجع البيانات من API. نستخدمها لتخزين البيانات داخل Redux Store.
rejected
تحدث عندما يفشل الطلب. نستخدمها لتخزين رسالة الخطأ وعرضها للمستخدم.
تثبيت Redux Toolkit و React Redux
npm install @reduxjs/toolkit react-redux
هذه الحزم تسمح لنا بإنشاء Redux Store وربطه بتطبيق React، واستخدام createAsyncThunk داخل المشروع.
هيكل الملفات المقترح
src/
app/
store.ts
features/
users/
usersSlice.ts
components/
UsersList.tsx
main.tsx
هذا التنظيم يساعد على فصل منطق Redux عن مكونات الواجهة، مما يجعل المشروع أكثر وضوحًا.
إنشاء createAsyncThunk لجلب المستخدمين من API
https://jsonplaceholder.typicode.com/users
قم بإنشاء ملف باسم usersSlice.ts:
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
type User = {
id: number;
name: string;
email: string;
};
type UsersState = {
users: User[];
loading: boolean;
error: string | null;
};
const initialState: UsersState = {
users: [],
loading: false,
error: null,
};
export const fetchUsers = createAsyncThunk(
"users/fetchUsers",
async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("فشل في جلب بيانات المستخدمين");
}
const data = await response.json();
return data as User[];
}
);
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false;
state.users = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || "حدث خطأ غير معروف";
});
},
});
export default usersSlice.reducer;
شرح الكود السابق بالتفصيل
قمنا بتحديد شكل بيانات المستخدم:
type User = {
id: number;
name: string;
email: string;
};
وهذا يساعد TypeScript على منع الأخطاء أثناء التعامل مع البيانات.
تعريف UsersState
قمنا بإنشاء حالة تحتوي على:
– users لتخزين البيانات – loading لمعرفة هل الطلب ما زال قيد التنفيذ – error لتخزين رسالة الخطأ
fetchUsers
هذه هي دالة createAsyncThunk التي تجلب البيانات من API:
export const fetchUsers = createAsyncThunk(
"users/fetchUsers",
async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await response.json();
return data as User[];
}
);
عند استدعاء fetchUsers، تقوم Redux Toolkit تلقائيًا بإطلاق pending، ثم fulfilled أو rejected حسب نتيجة الطلب.
ما فائدة extraReducers؟
مثال:
.addCase(fetchUsers.pending, (state) => {
state.loading = true;
})
هذا يعني: عند بداية الطلب اجعل loading يساوي true.
وعند النجاح:
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false;
state.users = action.payload;
})
نوقف التحميل ونخزن البيانات.
وعند الفشل:
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || "حدث خطأ";
})
نوقف التحميل ونخزن رسالة الخطأ.
إنشاء Redux Store
import { configureStore } from "@reduxjs/toolkit";
import usersReducer from "../features/users/usersSlice";
export const store = configureStore({
reducer: {
users: usersReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
هنا أضفنا usersReducer داخل Redux Store، وعرّفنا RootState و AppDispatch لاستخدامهما مع TypeScript.
ربط Redux Store مع React
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { store } from "./app/store";
import UsersList from "./components/UsersList";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Provider store={store}>
<UsersList />
</Provider>
</React.StrictMode>
);
مكون Provider يجعل Redux Store متاحًا لكل مكونات التطبيق.
عرض البيانات على واجهة المستخدم
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import type { AppDispatch, RootState } from "../app/store";
import { fetchUsers } from "../features/users/usersSlice";
function UsersList() {
const dispatch = useDispatch<AppDispatch>();
const { users, loading, error } = useSelector(
(state: RootState) => state.users
);
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
if (loading) {
return <h2>جاري تحميل البيانات...</h2>;
}
if (error) {
return <h2 style={{ color: "red" }}>{error}</h2>;
}
return (
<div>
<h2>قائمة المستخدمين</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<strong>{user.name}</strong>
<br />
<span>{user.email}</span>
</li>
))}
</ul>
</div>
);
}
export default UsersList;
شرح مكون UsersList
نستخدمه لاستدعاء fetchUsers:
dispatch(fetchUsers());
useSelector
نستخدمه لقراءة البيانات من Redux Store:
const { users, loading, error } = useSelector(
(state: RootState) => state.users
);
useEffect
نستخدمه لتشغيل طلب API عند فتح الصفحة:
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
عرض حالة التحميل
if (loading) {
return <h2>جاري تحميل البيانات...</h2>;
}
عرض حالة الفشل
if (error) {
return <h2 style={{ color: "red" }}>{error}</h2>;
}
عرض حالة النجاح
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
كيف يعمل Flow بالكامل؟
1- يتم تشغيل useEffect
2- يتم إرسال dispatch(fetchUsers())
3- تبدأ حالة pending ويصبح loading = true
4- تظهر رسالة جاري تحميل البيانات
5- إذا نجح الطلب تنتقل الحالة إلى fulfilled
6- يتم تخزين البيانات داخل users
7- تظهر البيانات على UI
8- إذا فشل الطلب تنتقل الحالة إلى rejected
9- تظهر رسالة الخطأ للمستخدم
تحسين معالجة الأخطاء باستخدام rejectWithValue
export const fetchUsers = createAsyncThunk(
"users/fetchUsers",
async (_, thunkAPI) => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
return thunkAPI.rejectWithValue("فشل الاتصال بالخادم");
}
const data = await response.json();
return data as User[];
} catch (error) {
return thunkAPI.rejectWithValue("حدث خطأ أثناء جلب البيانات");
}
}
);
ثم داخل rejected:
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
هذه الطريقة أفضل عندما تريد عرض رسالة خطأ واضحة للمستخدم.
الكود الكامل للمثال
// usersSlice.ts
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
type User = {
id: number;
name: string;
email: string;
};
type UsersState = {
users: User[];
loading: boolean;
error: string | null;
};
const initialState: UsersState = {
users: [],
loading: false,
error: null,
};
export const fetchUsers = createAsyncThunk(
"users/fetchUsers",
async (_, thunkAPI) => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
return thunkAPI.rejectWithValue("فشل الاتصال بالخادم");
}
const data = await response.json();
return data as User[];
} catch (error) {
return thunkAPI.rejectWithValue("حدث خطأ أثناء جلب البيانات");
}
}
);
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false;
state.users = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
});
},
});
export default usersSlice.reducer;
// store.ts
import { configureStore } from "@reduxjs/toolkit";
import usersReducer from "../features/users/usersSlice";
export const store = configureStore({
reducer: {
users: usersReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// UsersList.tsx
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import type { AppDispatch, RootState } from "../app/store";
import { fetchUsers } from "../features/users/usersSlice";
function UsersList() {
const dispatch = useDispatch<AppDispatch>();
const { users, loading, error } = useSelector(
(state: RootState) => state.users
);
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
if (loading) {
return <h2>جاري تحميل البيانات...</h2>;
}
if (error) {
return <h2 style={{ color: "red" }}>{error}</h2>;
}
return (
<div>
<h2>قائمة المستخدمين</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
<strong>{user.name}</strong>
<br />
<span>{user.email}</span>
</li>
))}
</ul>
</div>
);
}
export default UsersList;
أفضل استخدامات React createAsyncThunk
– جلب قائمة المستخدمين – عرض المنتجات من API – تسجيل الدخول – إرسال الطلبات – تحميل المقالات – جلب بيانات لوحة التحكم – التعامل مع أي عملية async داخل Redux
نصائح مهمة عند استخدام createAsyncThunk
– استخدم loading و error دائمًا مع أي API – لا تعرض واجهة فارغة أثناء انتظار البيانات – استخدم rejectWithValue لرسائل خطأ واضحة – اجعل كل API داخل slice مناسب له – استخدم TypeScript لتحديد شكل البيانات – لا تضع منطق UI داخل slice
رابط المقال
https://redux-toolkit.js.org/api/createAsyncThunk
الخلاصة
من خلالها يمكنك إدارة حالات التحميل والنجاح والفشل بدون تعقيد، وتخزين البيانات داخل Redux Store، ثم عرضها على واجهة المستخدم بطريقة واضحة.
في هذا المقال تعلمنا كيفية إنشاء createAsyncThunk، وكيفية استخدام extraReducers، وكيفية عرض loading و error و users داخل UI، بالإضافة إلى تحسين معالجة الأخطاء باستخدام rejectWithValue.
إذا كنت تعمل على تطبيق React يحتوي على API وبيانات خارجية، فإن تعلم React createAsyncThunk خطوة مهمة جدًا لبناء تطبيق أكثر احترافية وتنظيمًا.
لمزيد من المقالات : كيف تستخدم IndexedStack في Flutter لبناء تطبيقات سريعة وسلسة؟






