الآن سنبني:
EditStudent.jsxDartبفورم مستقل كامل مثل صفحة الإضافة تمامًا.
🧠 ما الفرق بين الإضافة والتعديل؟
| الإضافة | التعديل |
|---|---|
| حقول فارغة | حقول ممتلئة |
| addStudent | updateStudent |
| reset() | reset(studentData) |
| لا يوجد useParams | يوجد useParams |
🚀 الكود الكامل الاحترافي
EditStudent.jsx
import toast from "react-hot-toast";
import { useNavigate, useParams } from "react-router-dom";
import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
import { useDispatch, useSelector } from "react-redux";
import { updateStudent } from "../../features/students/studentsSlice";
import { fetchGrades } from "../../features/grades/gradesSlice";
import { fetchClassRooms } from "../../features/classrooms/ClassroomsSlice";
import { fetchSections } from "../../features/sections/SectionsSlice";
const EditStudent = () => {
// =========================================
// Hooks
// =========================================
const dispatch = useDispatch();
const navigate = useNavigate();
const { id } = useParams();
// =========================================
// Redux State
// =========================================
const { students } = useSelector(
(state) => state.students
);
const { grades } = useSelector(
(state) => state.grades
);
const { classrooms } = useSelector(
(state) => state.classrooms
);
const { sections } = useSelector(
(state) => state.sections
);
// =========================================
// البحث عن الطالب
// =========================================
const student = students.find(
(student) => student.documentId === id
);
// =========================================
// React Hook Form
// =========================================
const {
register,
handleSubmit,
watch,
reset,
formState: { errors }
} = useForm({
mode: "onChange"
});
// =========================================
// Fetch Data
// =========================================
useEffect(() => {
dispatch(fetchGrades());
dispatch(fetchClassRooms());
dispatch(fetchSections());
}, [dispatch]);
// =========================================
// تعبئة البيانات داخل الفورم
// =========================================
useEffect(() => {
if (student) {
reset({
st_code: student.st_code,
name: student.name,
age: student.age,
phone: student.phone,
email: student.email,
address: student.address,
blood: student.blood,
father: student.father,
mother: student.mother,
grade:
student.section?.classroom?.grade?.documentId || "",
classroom:
student.section?.classroom?.documentId || "",
section:
student.section?.documentId || ""
});
}
}, [student, reset]);
// =========================================
// مراقبة القيم
// =========================================
const selectedGrade = watch("grade");
const selectedClassroom = watch("classroom");
// =========================================
// فلترة الصفوف
// =========================================
const filteredClassRooms = useMemo(() => {
if (!selectedGrade) return [];
return classrooms.filter(
(classroom) =>
classroom.grade?.documentId === selectedGrade
);
}, [selectedGrade, classrooms]);
// =========================================
// فلترة الأقسام
// =========================================
const filteredSections = useMemo(() => {
if (!selectedClassroom) return [];
return sections.filter(
(section) =>
section.classroom?.documentId === selectedClassroom
);
}, [selectedClassroom, sections]);
// =========================================
// إرسال البيانات
// =========================================
const onSubmit = (data) => {
const studentData = {
st_code: data.st_code,
name: data.name,
age: data.age,
phone: data.phone,
email: data.email,
address: data.address,
blood: data.blood,
father: data.father,
mother: data.mother,
// 🔥 نخزن القسم فقط
section: data.section
};
dispatch(
updateStudent({
id: student.documentId,
data: studentData
})
)
.unwrap()
.then(() => {
toast.success("تم تعديل الطالب بنجاح");
navigate("/students");
})
.catch(() => {
toast.error("حدث خطأ أثناء التعديل");
});
};
// =========================================
// Loading State
// =========================================
if (!student) {
return (
<div className="alert alert-danger">
الطالب غير موجود
</div>
);
}
return (
<div>
<div className="row">
<div className="col-12">
<div className="card">
{/* ================================= */}
{/* Header */}
{/* ================================= */}
<div className="card-header">
<h3 className="card-title">
تعديل بيانات الطالب
</h3>
</div>
{/* ================================= */}
{/* Body */}
{/* ================================= */}
<div className="card-body">
<form onSubmit={handleSubmit(onSubmit)}>
{/* ================================= */}
{/* الصف الأول */}
{/* ================================= */}
<div className="row">
{/* كود الطالب */}
<div className="mb-3 col">
<label className="form-label">
كود الطالب
</label>
<input
type="text"
className="form-control"
{...register("st_code", {
required: "كود الطالب مطلوب"
})}
/>
{errors.st_code && (
<small className="text-danger">
{errors.st_code.message}
</small>
)}
</div>
{/* اسم الطالب */}
<div className="mb-3 col">
<label className="form-label">
اسم الطالب
</label>
<input
type="text"
className="form-control"
{...register("name", {
required: "اسم الطالب مطلوب",
minLength: {
value: 3,
message:
"الاسم يجب أن يكون 3 أحرف على الأقل"
}
})}
/>
{errors.name && (
<small className="text-danger">
{errors.name.message}
</small>
)}
</div>
{/* العمر */}
<div className="mb-3 col">
<label className="form-label">
العمر
</label>
<input
type="number"
className="form-control"
{...register("age", {
required: "العمر مطلوب"
})}
/>
{errors.age && (
<small className="text-danger">
{errors.age.message}
</small>
)}
</div>
</div>
{/* ================================= */}
{/* الصف الثاني */}
{/* ================================= */}
<div className="row">
{/* الهاتف */}
<div className="mb-3 col">
<label className="form-label">
الهاتف
</label>
<input
type="text"
className="form-control"
{...register("phone", {
required: "رقم الهاتف مطلوب"
})}
/>
{errors.phone && (
<small className="text-danger">
{errors.phone.message}
</small>
)}
</div>
{/* البريد */}
<div className="mb-3 col">
<label className="form-label">
البريد الإلكتروني
</label>
<input
type="email"
className="form-control"
{...register("email", {
required:
"البريد الإلكتروني مطلوب",
pattern: {
value: /^\S+@\S+$/i,
message:
"البريد الإلكتروني غير صالح"
}
})}
/>
{errors.email && (
<small className="text-danger">
{errors.email.message}
</small>
)}
</div>
</div>
{/* ================================= */}
{/* الصف الثالث */}
{/* ================================= */}
<div className="row">
{/* العنوان */}
<div className="mb-3 col">
<label className="form-label">
العنوان
</label>
<input
type="text"
className="form-control"
{...register("address")}
/>
</div>
{/* زمرة الدم */}
<div className="mb-3 col">
<label className="form-label">
زمرة الدم
</label>
<input
type="text"
className="form-control"
{...register("blood")}
/>
</div>
</div>
{/* ================================= */}
{/* الأب والأم */}
{/* ================================= */}
<div className="row">
<div className="mb-3 col">
<label className="form-label">
اسم الأب
</label>
<input
type="text"
className="form-control"
{...register("father")}
/>
</div>
<div className="mb-3 col">
<label className="form-label">
اسم الأم
</label>
<input
type="text"
className="form-control"
{...register("mother")}
/>
</div>
</div>
{/* ================================= */}
{/* المرحلة / الصف / القسم */}
{/* ================================= */}
<div className="row mt-3">
{/* المرحلة */}
<div className="col">
<label className="form-label">
المرحلة
</label>
<select
className="form-select"
{...register("grade", {
required: "المرحلة مطلوبة"
})}
>
<option value="">
اختر المرحلة
</option>
{grades?.map((grade) => (
<option
key={grade.documentId}
value={grade.documentId}
>
{grade.name}
</option>
))}
</select>
</div>
{/* الصف */}
<div className="col">
<label className="form-label">
الصف
</label>
<select
className="form-select"
{...register("classroom", {
required: "الصف مطلوب"
})}
>
<option value="">
اختر الصف
</option>
{filteredClassRooms.map((classroom) => (
<option
key={classroom.documentId}
value={classroom.documentId}
>
{classroom.name}
</option>
))}
</select>
</div>
{/* القسم */}
<div className="col">
<label className="form-label">
القسم
</label>
<select
className="form-select"
{...register("section", {
required: "القسم مطلوب"
})}
>
<option value="">
اختر القسم
</option>
{filteredSections.map((section) => (
<option
key={section.documentId}
value={section.documentId}
>
{section.name}
</option>
))}
</select>
{errors.section && (
<small className="text-danger">
{errors.section.message}
</small>
)}
</div>
</div>
{/* ================================= */}
{/* زر الحفظ */}
{/* ================================= */}
<button
type="submit"
className="btn btn-primary mt-5"
>
حفظ التعديلات
</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
};
export default EditStudent;Dartالراوت الخاص بالتعديل
<Route
path="/students/:id/edit"
element={<EditStudent />}
/>Dart🚀 زر التعديل داخل الجدول
<Link
to={`/students/${student.documentId}/edit`}
className="btn btn-primary btn-sm"
>
تعديل
</Link>Dartالآن ماذا ستلاحظ؟
ستلاحظ مباشرة:
كمية التكرار الكبيرة بين add/editDartوهذا ممتاز جدًا تعليميًا 👌
💡 لاحقًا عندما ندمج
ستفهم مباشرة:
- لماذا reusable form مهم
- لماذا abstraction مهم
- لماذا الشركات الكبيرة لا تكرر الفورمات
🔥
🎯 أهم شيء يجب أن تفهمه هنا
التعديل يحتاج:
reset()Dartوليس:
defaultValues فقطDart🧠 لماذا؟
لأن بيانات الطالب تأتي:
بعد renderDartوليس أثناء أول render.