تعديل بيانات الطالب

الآن سنبني:
EditStudent.jsx
Dart

بفورم مستقل كامل مثل صفحة الإضافة تمامًا.

🧠 ما الفرق بين الإضافة والتعديل؟
الإضافةالتعديل
حقول فارغةحقول ممتلئة
addStudentupdateStudent
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/edit
Dart

وهذا ممتاز جدًا تعليميًا 👌

💡 لاحقًا عندما ندمج

ستفهم مباشرة:

  • لماذا reusable form مهم
  • لماذا abstraction مهم
  • لماذا الشركات الكبيرة لا تكرر الفورمات

🔥

🎯 أهم شيء يجب أن تفهمه هنا

التعديل يحتاج:

reset()
Dart

وليس:

defaultValues فقط
Dart
🧠 لماذا؟

لأن بيانات الطالب تأتي:

بعد render
Dart

وليس أثناء أول render.