شارك المقالة

شرح React createAsyncThunk لجلب البيانات من API وعرض حالات التحميل والنجاح والفشل


عند بناء تطبيقات React الحقيقية، غالبًا ستحتاج إلى جلب البيانات من API خارجي، مثل عرض المنتجات أو المستخدمين أو الطلبات أو المقالات. وهنا تظهر أهمية React createAsyncThunk داخل Redux Toolkit، لأنها تساعدك على تنظيم عمليات جلب البيانات بطريقة واضحة، مع التعامل مع حالات الانتظار والنجاح والفشل بسهولة.

بدلًا من كتابة أكواد كثيرة لإدارة حالة التحميل والخطأ والبيانات، توفر لنا Redux Toolkit دالة createAsyncThunk التي تنشئ بشكل تلقائي ثلاث حالات مهمة لأي طلب API وهي: pending و fulfilled و rejected. وهذا يجعل الكود أكثر ترتيبًا وأسهل في الصيانة.

في هذا المقال سنشرح بالتفصيل كيفية استخدام React createAsyncThunk مع API، وكيفية انتظار البيانات، وعرض رسالة تحميل أثناء الطلب، ثم عرض البيانات عند النجاح، أو رسالة خطأ عند الفشل.

ما هي createAsyncThunk؟

دالة createAsyncThunk هي أداة من Redux Toolkit تُستخدم للتعامل مع العمليات غير المتزامنة مثل API requests.

حسب التوثيق الرسمي، تستقبل createAsyncThunk ثلاثة أجزاء أساسية: اسم الـ action، ودالة async تُرجع Promise، وخيارات إضافية اختيارية. كما تقوم بإنشاء حالات lifecycle تلقائيًا للطلب: pending عند بداية الطلب، fulfilled عند نجاحه، و rejected عند فشله.

هذا يجعلها مناسبة جدًا لجلب البيانات من API داخل تطبيقات React بطريقة منظمة.

ما الحالات التي تنشئها createAsyncThunk؟

عند استخدام 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

سنستخدم مثالًا بسيطًا لجلب المستخدمين من 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;

شرح الكود السابق بالتفصيل

تعريف نوع User
قمنا بتحديد شكل بيانات المستخدم:

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؟

نستخدم extraReducers للتعامل مع الحالات التي تنشئها createAsyncThunk.

مثال:

.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

قم بإنشاء ملف 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;

هنا أضفنا usersReducer داخل Redux Store، وعرّفنا RootState و AppDispatch لاستخدامهما مع TypeScript.

ربط Redux Store مع React

في ملف main.tsx:

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 متاحًا لكل مكونات التطبيق.

عرض البيانات على واجهة المستخدم

الآن ننشئ مكون 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;

شرح مكون UsersList

useDispatch
نستخدمه لاستدعاء 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

يمكن استخدام 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

يمكن استخدام React createAsyncThunk في حالات كثيرة مثل:

– جلب قائمة المستخدمين – عرض المنتجات من API – تسجيل الدخول – إرسال الطلبات – تحميل المقالات – جلب بيانات لوحة التحكم – التعامل مع أي عملية async داخل Redux

نصائح مهمة عند استخدام createAsyncThunk


– استخدم loading و error دائمًا مع أي API – لا تعرض واجهة فارغة أثناء انتظار البيانات – استخدم rejectWithValue لرسائل خطأ واضحة – اجعل كل API داخل slice مناسب له – استخدم TypeScript لتحديد شكل البيانات – لا تضع منطق UI داخل slice

رابط المقال


https://redux-toolkit.js.org/api/createAsyncThunk

الخلاصة

تعد React createAsyncThunk من أهم الأدوات داخل Redux Toolkit للتعامل مع API والعمليات غير المتزامنة بشكل منظم وسهل.

من خلالها يمكنك إدارة حالات التحميل والنجاح والفشل بدون تعقيد، وتخزين البيانات داخل Redux Store، ثم عرضها على واجهة المستخدم بطريقة واضحة.

في هذا المقال تعلمنا كيفية إنشاء createAsyncThunk، وكيفية استخدام extraReducers، وكيفية عرض loading و error و users داخل UI، بالإضافة إلى تحسين معالجة الأخطاء باستخدام rejectWithValue.

إذا كنت تعمل على تطبيق React يحتوي على API وبيانات خارجية، فإن تعلم React createAsyncThunk خطوة مهمة جدًا لبناء تطبيق أكثر احترافية وتنظيمًا.

لمزيد من المقالات : كيف تستخدم IndexedStack في Flutter لبناء تطبيقات سريعة وسلسة؟
شاهد أيضًا
مقالات ذات صلة

🚫 مانع الإعلانات مفعل

يجب إيقاف مانع الإعلانات لاستكمال تصفح الموقع