Pagination

1- Slice Code

import { createSlice , createAsyncThunk } from "@reduxjs/toolkit";
import api from "../api";

const initialState = {
    data:[],
    isLoading: false,
    error: null,
    meta: null,
}

export const getAllBooks = createAsyncThunk("books-actions" , async ({ page = 1, pageSize = 6 } = {})=> {
    // const {data} = await api.get("/books?populate=*");
    const {data} = await api.get(`/books?pagination[page]=${page}&pagination[pageSize]=${pageSize}&populate=*`);
    return data;
});


const booksSlice = createSlice({
    name: "booksSlice",
    initialState,
    extraReducers: (builder)=> {
        builder.addCase(getAllBooks.pending , (state)=> {
            state.isLoading = true;
            state.error = null;
        })

        .addCase(getAllBooks.fulfilled , (state , action)=> {

            state.data = action.payload.data;
            state.isLoading = false;
            state.meta = action.payload.meta;

        })

        .addCase(getAllBooks.rejected , (state , action)=> {
            state.isLoading = false;
            state.error = action.error.message || "حدث خطا في البيانات"
        })
    }

});

export const booksReducer = booksSlice.reducer
JavaScript

2- Book Page

 const [currentPage, setCurrentPage] = useState(1);
  const pageSize = 3;
  
  
    useEffect(() => {
    dipatch(getAllBooks({ page: currentPage, pageSize }))
  }, [dipatch, data.length, currentPage]);
  
  const totalPages = meta?.pagination?.pageCount || 1;
JavaScript

3- أزرار التنقل

     {/* Pagination Buttons */}
      <div className="d-flex justify-content-center mt-4">

        {[...Array(totalPages)].map((_, index) => (
          <button
            key={index}
            className={`btn mx-1 ${currentPage === index + 1
                ? "btn-primary"
                : "btn-outline-primary"
              }`}
            onClick={() => setCurrentPage(index + 1)}
          >
            {index + 1}
          </button>
        ))}

      </div>
JavaScript

سنرجع الآن ونشرح ربط رقم الصفحة بالـ URL بالتفصيل العميق خطوة خطوة حتى تفهمه معماريًا بالكامل.

🎯 الهدف النهائي

نريد أن يصبح الرابط هكذا:

/books?page=3
JavaScript

وعند:

  • الضغط على زر صفحة 4 → يتغير الرابط
  • عمل Refresh → تبقى في نفس الصفحة
  • الضغط Back → يرجع للصفحة السابقة

🔥 هذا هو السلوك الاحترافي.

🧠 أولاً: لماذا لا نستخدم useState فقط؟

لو كتبنا:

const [currentPage, setCurrentPage] = useState(1);
JavaScript

المشكلة:

  • عند Refresh → يرجع إلى 1
  • الرابط لا يتغير
  • لا يمكن مشاركة الصفحة 3 مباشرة

لذلك نستخدم:

🔹 useSearchParams
بدل useState

🏗 الخطوة 1: قراءة الصفحة من الرابط
import { useSearchParams } from "react-router-dom";
const [searchParams, setSearchParams] = useSearchParams();
const currentPage = Number(searchParams.get("page")) || 1;
JavaScript
🧠 ماذا يحدث هنا بالتفصيل؟
1️⃣ useSearchParams()

هذا Hook من React Router.

يرجع Array:

[URLSearchParams, setSearchParams]
JavaScript
  • الأول: كائن يمثل query params
  • الثاني: دالة لتعديلهم
2️⃣ searchParams.get(“page”)

لو الرابط:

/books?page=4
JavaScript

فإن:

searchParams.get("page")
JavaScript

يرجع:

"4"
JavaScript

⚠️ يرجع String وليس Number.

3️⃣ Number()

نحوّلها إلى رقم:

Number("4") → 4
JavaScript

4️⃣ || 1

لو لم يوجد page في الرابط:

/books
JavaScript

فـ get(“page”) → null
Number(null) → 0
نريد أن تكون الصفحة الافتراضية = 1

لذلك نكتب:

Number(searchParams.get("page")) || 1;
JavaScript

🔥 الآن currentPage دائمًا رقم صحيح.

🏗 الخطوة 2: جلب البيانات عند تغير الصفحة
useEffect(() => {
  dispatch(getAllBooks({ page: currentPage, pageSize: 6 }));
  }, [dispatch, currentPage]);
JavaScript
🧠 ماذا يحدث هنا؟
  • عند تحميل الصفحة
  • أو عند تغير currentPage
  • يتم استدعاء API

⚠️ currentPage يتغير عندما يتغير الرابط
وليس عندما نغير state يدويًا.

🏗 الخطوة 3: تغيير الصفحة بتغيير الرابط

بدل:

setCurrentPage(3);
JavaScript

نكتب:

setSearchParams({ page: 3 });
JavaScript
🧠 ماذا يفعل setSearchParams؟

لو كنا في:

/books?page=2
JavaScript

وعملنا:

setSearchParams({ page: 3 });
JavaScript

الرابط يصبح:

/books?page=3
JavaScript

ثم:

  • currentPage يُعاد حسابه
  • useEffect يعمل
  • يتم جلب الصفحة الجديدة

🔥 الرابط هو مصدر الحقيقة.

🏗 استخدامه داخل Pagination
<button
  onClick={() => setSearchParams({ page: index + 1 })}>
    {index + 1}
</button>
JavaScript
🧠 ماذا يحدث عند الضغط؟

مثلاً ضغط المستخدم على 5:

1️⃣ يتم تنفيذ setSearchParams({ page: 5 })
2️⃣ الرابط يتغير
3️⃣ React Router يعيد render
4️⃣ currentPage يصبح 5
5️⃣ useEffect يعمل
6️⃣ dispatch(getAllBooks({ page: 5 }))

🔥 دورة كاملة مترابطة.