بدل أن يكون لديك فقط:
عرض الطلاب
تعديل
حذفJSXسيصبح لديك أيضًا:
عرض التفاصيلJSX🎯 الفكرة
في جدول الطلاب تضيف زر:
<Link
to={`/students/${student.documentId}`}
className="btn btn-info btn-sm"
>
التفاصيل
</Link>JSX🚀 الراوت
داخل App.jsx
<Route
path="/students/:id"
element={<StudentDetails />}
/>JSX🎯 إنشاء الصفحة
pages/students/StudentDetails.jsxJSX🚀 الكود الأساسي
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
const StudentDetails = () => {
const { id } = useParams();
const { students } = useSelector(
(state) => state.students
);
const student = students.find(
(student) => student.documentId === id
);
if (!student) {
return (
<div className="alert alert-danger">
الطالب غير موجود
</div>
);
}
return (
<div className="container-fluid">
<div className="card">
<div className="card-header">
<h3 className="card-title">
تفاصيل الطالب
</h3>
</div>
<div className="card-body">
<div className="row">
<div className="col-md-6">
<p>
<strong>كود الطالب:</strong>
{" "}
{student.st_code}
</p>
<p>
<strong>اسم الطالب:</strong>
{" "}
{student.name}
</p>
<p>
<strong>العمر:</strong>
{" "}
{student.age}
</p>
<p>
<strong>الهاتف:</strong>
{" "}
{student.phone}
</p>
<p>
<strong>البريد الإلكتروني:</strong>
{" "}
{student.email}
</p>
</div>
<div className="col-md-6">
<p>
<strong>العنوان:</strong>
{" "}
{student.address}
</p>
<p>
<strong>زمرة الدم:</strong>
{" "}
{student.blood}
</p>
<p>
<strong>اسم الأب:</strong>
{" "}
{student.father}
</p>
<p>
<strong>اسم الأم:</strong>
{" "}
{student.mother}
</p>
</div>
</div>
</div>
</div>
</div>
);
};
export default StudentDetails;JSX🎯 إظهار القسم والصف والمرحلة
بما أنك خزنت:
sectionJSXفقط
يمكنك الوصول لباقي العلاقات هكذا:
<p>
<strong>القسم:</strong>
{" "}
{student.section?.name}
</p>
<p>
<strong>الصف:</strong>
{" "}
{student.section?.classroom?.name}
</p>
<p>
<strong>المرحلة:</strong>
{" "}
{student.section?.classroom?.grade?.name}
</p>JSX⚠️ مهم جداً
لكي تعمل الأسطر السابقة يجب أن يكون fetchStudents يحتوي على populate للعلاقات.
مثال في Strapi:
/students?populate=section.classroom.gradeJSXأو
/students?populate=*JSXأثناء التطوير.
🚀 الشكل الاحترافي الذي أنصح به
بدل عرض البيانات كسطور عادية:
اعمل بطاقتين (Cards)
البطاقة الأولى
المعلومات الشخصيةJSX- الاسم
- الكود
- العمر
- الهاتف
- الإيميل
البطاقة الثانية
المعلومات الأكاديميةJSX- المرحلة
- الصف
- القسم
البطاقة الثالثة
معلومات ولي الأمرJSX- الأب
- الأم
- العنوان
🎯 خطوة أكثر احترافية
إذا كنت تنوي لاحقًا إضافة:
- صورة الطالب
- السجل الدراسي
- الحضور والغياب
- الأقساط
فأنصحك من الآن أن تجعل صفحة التفاصيل تعتمد على:
fetchSingleStudent(id)JSXوليس:
students.find(...)JSXلأن عند تحديث الصفحة (F5) ستختفي البيانات إذا لم تكن موجودة في Redux.
لذلك في الأنظمة الحقيقية غالبًا يكون لديك:
/student/abc123JSXثم:
dispatch(fetchSingleStudent(id))JSXويتم جلب الطالب مباشرة من الـ API.
وهذه هي الطريقة التي أوصي بها عندما ننتهي من CRUD الأساسي. 🚀
نعم اختفتالمعلومات عند التحديث كيف يمكن حلها اكتب بالتفصيل
ممتاز 🔥 وهذه من أهم المشاكل التي تواجه أي شخص يتعلم React + Redux لأول مرة.
🎯 لماذا تختفي البيانات عند F5؟
لنفترض أنك دخلت إلى:
/students/abc123JSXثم صفحة التفاصيل تعمل هكذا:
const { students } = useSelector(
(state) => state.students
);
const student = students.find(
(student) => student.documentId === id
);JSXقبل التحديث
Redux يحتوي:
students = [
{ documentId: "abc123", name: "محمد" }
]JSXفيجد الطالب ويعرضه.
بعد التحديث F5
المتصفح يعيد تحميل التطبيق بالكامل.
Redux يعود إلى:
initialState = {
students: []
}JSXفتصبح:
students = []JSXالنتيجة
هذا السطر:
students.find(...)JSXيرجع:
undefinedJSXفتختفي البيانات.
🎯 ما هو الحل الاحترافي؟
بدلاً من الاعتماد على:
students.find(...)JSXنجلب الطالب مباشرة من الـ API.
المعمارية الصحيحة
عند فتح:
/students/abc123JSXيجب أن يحدث:
1- قراءة id من الرابط
2- إرسال طلب API
3- جلب الطالب
4- عرض البياناتJSX🚀 إنشاء fetchSingleStudent
داخل studentsSlice
thunk
export const fetchSingleStudent = createAsyncThunk(
"students/fetchSingleStudent",
async (id, thunkAPI) => {
try {
const response = await axiosInstance.get(
`/students/${id}?populate=section.classroom.grade`
);
return response.data.data;
} catch (error) {
return thunkAPI.rejectWithValue(
error.response?.data || error.message
);
}
}
);JSXإضافة State جديد
بدل:
const initialState = {
students: [],
loading: false,
error: null
};JSXاجعل:
const initialState = {
students: [],
student: null,
loading: false,
error: null
};JSXaddCase
.addCase(fetchSingleStudent.pending, (state) => {
state.loading = true;
})
.addCase(fetchSingleStudent.fulfilled, (state, action) => {
state.loading = false;
state.student = action.payload;
})
.addCase(fetchSingleStudent.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
})JSX🎯 صفحة التفاصيل
الآن لا نستخدم:
find()JSXأبداً.
StudentDetails.jsx
const { id } = useParams();
const dispatch = useDispatch();
const {
student,
loading
} = useSelector(
(state) => state.students
);JSXجلب البيانات
useEffect(() => {
dispatch(fetchSingleStudent(id));
}, [dispatch, id]);JSXحالة التحميل
if (loading) {
return <h3>جاري التحميل...</h3>;
}JSXإذا لم يوجد طالب
if (!student) {
return (
<div className="alert alert-danger">
الطالب غير موجود
</div>
);
}JSXعرض البيانات
<h2>{student.name}</h2>
<p>{student.phone}</p>
<p>{student.email}</p>
<p>{student.section?.name}</p>
<p>{student.section?.classroom?.name}</p>
<p>{student.section?.classroom?.grade?.name}</p>JSX🎯 لماذا هذه الطريقة أفضل؟
لأنها تعمل في كل الحالات:
| الحالة | النتيجة |
|---|---|
| الدخول من صفحة الطلاب | ✅ |
| نسخ الرابط وفتحه مباشرة | ✅ |
| F5 | ✅ |
| مشاركة الرابط مع شخص آخر | ✅ |
🔥 نفس الفكرة يجب تطبيقها على صفحة التعديل
بدلاً من:
const student = students.find(...)JSXاستخدم:
fetchSingleStudent(id)JSXثم:
reset(student)JSXعند وصول البيانات.
🎯 لماذا الشركات الكبيرة تفعل ذلك؟
لأن الرابط:
/students/abc123JSXيجب أن يكون مستقلاً بذاته.
أي أن الصفحة قادرة على العمل حتى لو:
Redux فارغJSXأو:
تم تحديث الصفحةJSXوهذا ما يسمى:
Route Driven Data FetchingJSXوهو الأسلوب الاحترافي المستخدم في أغلب الأنظمة الحديثة. 🚀