بناء قائمة تسوق تفاعلية باستخدام React Hooks: دليل شامل للمبتدئين

دقائق القراءة: 9

في هذا الدليل الشامل، سنخوض رحلة ممتعة في عالم React لبناء تطبيق قائمة تسوق تفاعلي باستخدام React Hooks. سواء كنت مبتدئًا في React أو تسعى لتعزيز مهاراتك، ستتعلم هنا كيفية التعامل مع كائنات الحالة المعقدة، تحديث أجزاء محددة من الحالة، والاستفادة من الحالة الحالية لحساب حالات جديدة بكفاءة. هدفنا هو تقديم محتوى حصري وقيم يساعدك على فهم آليات عمل React بعمق وتطبيقها عمليًا.

ماذا سنبني في هذا المشروع؟

سنقوم بإنشاء تطبيق قائمة تسوق يتيح للمستخدمين إدارة مشترياتهم بسهولة. سيتضمن التطبيق الميزات الأساسية التالية التي سنقوم بتطويرها خطوة بخطوة:

  • إضافة عناصر جديدة إلى القائمة.
  • تعديل كميات العناصر الموجودة (زيادة أو نقصان).
  • تحديد العناصر كمكتملة أو غير مكتملة.
  • حساب وعرض إجمالي عدد العناصر في القائمة بشكل ديناميكي.

شاهد كيف سيبدو المنتج النهائي:

عرض توضيحي لتطبيق قائمة التسوق التفاعلي المبني باستخدام React Hooks

جرب بنفسك: تحديات التطبيق

إذا كنت تفضل خوض التحدي بنفسك أولاً قبل متابعة الشرح، فإليك السيناريوهات الرئيسية التي يجب تحقيقها في التطبيق. يمكنك أيضًا الحصول على الكود الأولي (Starter Code) من القسم التالي:

  1. يجب أن يتمكن المستخدم من إضافة عناصر جديدة إلى القائمة عن طريق الكتابة في حقل الإدخال والنقر على رمز "+".
  2. يجب أن يتمكن المستخدم من زيادة/تقليل كميات عنصر معين.
  3. يجب أن يعرض الإجمالي الكمية الكلية لجميع العناصر في القائمة.

الكود الأولي والمصادر المساعدة

يمكنك الحصول على الكود الأولي للمشروع من مستودع GitHub عبر هذا الرابط: GitHub Repository.

(ملاحظة: المقال الأصلي يشير إلى وجود فيديو توضيحي، ولكن لا يوجد رابط مباشر له في النص المقدم. نركز هنا على المحتوى النصي والبرمجي.)

عرض قائمة العناصر باستخدام React

الخطوة الأولى في بناء تطبيقنا هي عرض قائمة من العناصر. إذا كنت تعمل مع الكود الأولي، فستلاحظ أننا قمنا بتعريف كائن حالة (state object) باستخدام useState:

 const [items, setItems] = useState([]);

سنقوم بتهيئة هذه الحالة بمصفوفة من الكائنات (array of objects). بعد ذلك، سنستخدم دالة map للتكرار (loop) على هذه القائمة وعرض العناصر. استبدل السطر أعلاه بالتعريف التالي لتهيئة القائمة الأولية:

 const [items, setItems] = useState([
     { itemName: 'item 1', quantity: 1, isSelected: false },
     { itemName: 'item 2', quantity: 3, isSelected: true },
     { itemName: 'item 3', quantity: 2, isSelected: false },
 ]);

ستلاحظ أن كل عنصر في المصفوفة هو كائن (object). يمثل هذا الكائن كل عنصر (أو صف) ويحتوي على الخصائص التي سنحتاجها للعرض:

  • اسم العنصر (itemName).
  • الكمية (quantity).
  • علامة (flag) سنستخدمها لعرض “علامة صح” (tick) أو “دائرة فارغة” (empty circle) للإشارة إلى تحديد العنصر.

السبب في وضع هذه القائمة في الحالة كمصفوفة هو أن القائمة ستتغير ديناميكيًا. عندما نرغب في تغيير القائمة، ما علينا سوى إضافة عناصر إليها أو إزالتها منها، وسيقوم React تلقائيًا بتحديث واجهة المستخدم (UI) لنا.

الآن، نحتاج فقط إلى إضافة دالة map إلى جزء JSX الخاص بنا للتكرار على هذه المصفوفة وعرض خصائصها على واجهة المستخدم. استبدل الـ div الخاص بقائمة العناصر بما يلي:

<div className='item-list'>
    {items.map((item, index) => (
        <div className='item-container'>
            <div className='item-name' onClick={() => toggleComplete(index)}>
                {item.isSelected ? (
                    <>
                        <FontAwesomeIcon icon={faCheckCircle} />
                        <span className='completed'> {item.itemName} </span>
                    </>
                ) : (
                    <>
                        <FontAwesomeIcon icon={faCircle} />
                        <span> {item.itemName} </span>
                    </>
                )}
            </div>
            <div className='quantity'>
                <button>
                    <FontAwesomeIcon icon={faChevronLeft} onClick={() => handleQuantityDecrease(index)} />
                </button>
                <span> {item.quantity} </span>
                <button>
                    <FontAwesomeIcon icon={faChevronRight} onClick={() => handleQuantityIncrease(index)} />
                </button>
            </div>
        </div>
    ))}
</div>

دعنا نشرح هذا الجزء من الكود. لقد أدخلنا دالة map التي ستقوم بالتكرار على العناصر في مصفوفة items وعرض مجموعة من JSX لكل عنصر. تذكر أن دالة map توفر لنا الكائن الحالي الذي يتم التكرار عليه كمتغير، مما يتيح لنا الوصول إلى خصائصه.

نحن نستخدم عامل التشغيل الثلاثي (ternary operator) للتحقق من متغير item.isSelected. إذا كان المتغير true، نعرض “علامة صح” مع خط يتوسط النص (strikethrough). إذا كانت القيمة false، نعرض “دائرة فارغة” مع اسم العنصر. كما نعرض الكمية (quantity) لهذا العنصر المحدد.

تخزين مدخلات المستخدم في الحالة

بعد أن أصبح لدينا بعض العناصر معروضة، سنمكن المستخدم من إضافة عناصر جديدة إلى القائمة. لن تكون قائمة تسوق جيدة إذا لم يتمكن المستخدمون من إضافة أشياء إليها! ستلاحظ في الكود الأولي أننا قمنا بتضمين حقل إدخال (input):

<div className='add-item-box'>
    <input className='add-item-input' placeholder='Add an item...' />
    <FontAwesomeIcon icon={faPlus} />
</div>

في الوقت الحالي، هذا الحقل لا يقوم بالكثير. نحتاج إلى منح التحكم إلى React حتى نتمكن من التعامل بسهولة مع القيمة التي أدخلها المستخدم. للقيام بذلك، سننشئ قيمة حالة جديدة (new state value) للاحتفاظ بقيمة ما كتبه المستخدم، وسنضيف حدث onChange لتغيير هذه القيمة.

أضف كائن حالة جديد، وقم بتهيئته بسلسلة نصية فارغة (empty string):

 const [inputValue, setInputValue] = useState('');

الآن، داخل حقل الإدخال، أضف خاصية value ودالة onChange على النحو التالي:

<input value={inputValue} onChange={(event) => setInputValue(event.target.value)} className='add-item-input' placeholder='Add an item...' />

كلما كتب المستخدم، يتم استدعاء حدث onChange. يمرر React كائن الحدث (event object) تلقائيًا لنا، حتى نتمكن من الحصول على القيمة التي كتبها المستخدم منه عبر event.target.value. ثم نأخذ هذه القيمة ونستدعي setInputValue لتعيين ما كتبه المستخدم في الحالة. بعد ذلك، نضبط قيمة حقل الإدخال لتكون أي قيمة مخزنة في متغير حالة inputValue.

إضافة عنصر جديد إلى القائمة

الآن أصبح من المنطقي إضافة القيمة التي كتبها المستخدم إلى القائمة. بما أننا نعرف القائمة الحالية، ونعرف ما كتبه المستخدم (لقد وضعنا كل شيء في الحالة!)، فكل ما علينا فعله هو دمج هذه الأشياء معًا. بعبارة أخرى، سنضيف قيمة inputValue إلى مصفوفة items.

ابدأ بإنشاء دالة جديدة، والتي سيتم استدعاؤها عندما ينقر المستخدم على أيقونة "+":

 const handleAddButtonClick = () => {
     const newItem = {
         itemName: inputValue,
         quantity: 1,
         isSelected: false,
     };
     const newItems = [...items, newItem];
     setItems(newItems);
     setInputValue('');
     calculateTotal(); // استدعاء دالة تحديث الإجمالي
 };

ما تفعله هذه الدالة هو:

  • إنشاء كائن جديد يسمى newItem وهو ما يتم دفعه إلى المصفوفة. نضبط itemName ليكون قيمة inputValue، ونضبط quantity افتراضيًا على 1، ونضبط قيمة isSelected المنطقية افتراضيًا على false.
  • نسخ المصفوفة الموجودة (نقوم بذلك لتجنب تغيير الحالة الأصلية مباشرةً – mutating state)، وإضافة كائن newItem الخاص بنا إلى النهاية.
  • دفع المصفوفة الجديدة إلى الحالة عبر setItems(newItems).
  • أخيرًا، إعادة تعيين inputValue إلى سلسلة نصية فارغة حتى يتمكن المستخدم من الكتابة وإضافة المزيد من العناصر.
  • (مضاف) استدعاء دالة calculateTotal() لتحديث الإجمالي بعد إضافة عنصر جديد.

الآن بعد أن أصبح لدينا دالة، ما علينا سوى ربطها بالزر الخاص بنا:

<FontAwesomeIcon icon={faPlus} onClick={() => handleAddButtonClick()} />

إذا قمت بتشغيل الكود، وكتبت شيئًا في حقل الإدخال، ونقرت على أيقونة “الزائد”، فيجب أن يتم إضافته إلى القائمة. أحسنت!

تبديل حالة تحديد العنصر (Toggle Item)

الآن سننظر في كيفية تبديل حالة عنصر للإشارة إلى أنه تم تحديده أو إلغاء تحديده. نعلم أن كل عنصر في المصفوفة/القائمة يحتوي على متغير isSelected، لذا كل ما علينا فعله هو تحديث هذا المتغير عند النقر على العنصر.

أنشئ دالة جديدة على النحو التالي:

 const toggleComplete = (index) => {
     const newItems = [...items];
     newItems[index].isSelected = !newItems[index].isSelected;
     setItems(newItems);
 };

تأخذ هذه الدالة index كمعامل. يتم توفير الـ index لنا بواسطة دالة map، ويشير إلى الموضع الحالي للعنصر في المصفوفة. ثم نستخدم هذا الـ index للحصول على الكائن من المصفوفة، ونضبط متغير isSelected ليكون عكس قيمته الحالية (true يصبح false والعكس). ثم نضع العناصر المحدثة مرة أخرى في الحالة.

يتسبب هذا في إعادة React لرسم المكون (rerender the component) وعرض إما “دائرة محددة” (checked circle) أو “دائرة فارغة” (empty circle) لكل عنصر اعتمادًا على هذه العلامة (تذكر أننا كتبنا منطق العامل الثلاثي لهذا في وقت سابق).

لجعل كل هذا يعمل، نحتاج فقط إلى استدعاء toggleComplete عندما ينقر المستخدم على الدائرة:

حدث الـ div الخاص بـ itemName على النحو التالي:

<div className='item-name' onClick={() => toggleComplete(index)}>
    // ...الكود الآخر
</div>

لاحظ أننا نمرر الـ index الذي نحصل عليه من دالة map. هذا يخبرنا بالموضع الحالي في المصفوفة الذي نحن فيه. قم بتشغيل الكود ويجب أن تكون قادرًا على “تحديد” عنصر. نجاح!

تحديث الكميات

سنتبع نهجًا مشابهًا لتحديث الكميات. سنبدأ بزيادة الكمية. أضف دالة على النحو التالي:

 const handleQuantityIncrease = (index) => {
     const newItems = [...items];
     newItems[index].quantity++;
     setItems(newItems);
     calculateTotal(); // استدعاء دالة تحديث الإجمالي
 };

ستلاحظ أن هذا مشابه لدالة toggleComplete:

  • نستخدم الـ index للحصول على العنصر/الكائن من المصفوفة.
  • نزيد الكمية (quantity++).
  • نضع كل شيء مرة أخرى في الحالة.
  • (مضاف) استدعاء دالة calculateTotal() لتحديث الإجمالي بعد زيادة الكمية.

الآن نحتاج فقط إلى تحديث الزر الخاص بنا لاستدعاء هذه الدالة:

<button>
    <FontAwesomeIcon icon={faChevronRight} onClick={() => handleQuantityIncrease(index)} />
</button>

جرب هذا، ويجب أن تكون قادرًا على النقر على “السهم الأيمن” ويجب أن تزداد الكمية.

سيكون التعامل مع تقليل الكمية مشابهًا أيضًا. أنشئ دالة على النحو التالي:

 const handleQuantityDecrease = (index) => {
     const newItems = [...items];
     newItems[index].quantity--;
     setItems(newItems);
     calculateTotal(); // استدعاء دالة تحديث الإجمالي
 };

ما نقوم به هنا:

  • نستخدم الـ index للحصول على العنصر/الكائن من المصفوفة.
  • نقلل الكمية (quantity--).
  • نضع كل شيء مرة أخرى في الحالة.
  • (مضاف) استدعاء دالة calculateTotal() لتحديث الإجمالي بعد تقليل الكمية.

حساب الكمية الإجمالية

حسنًا، تطبيقنا يبدو جيدًا. آخر شيء نحتاج إلى القيام به هو تحديث الكمية الإجمالية في الأسفل. أول شيء سنفعله هو إنشاء قيمة حالة (state value). سيتم استخدام هذا للاحتفاظ بالكميات الإجمالية وعرضها:

 const [totalItemCount, setTotalItemCount] = useState(0);

ملاحظة: لقد قمت بتغيير القيمة الافتراضية من 6 إلى 0 ليكون أكثر مرونة، حيث أن القيمة 6 كانت تعتمد على القائمة الأولية الثابتة. يفضل حساب الإجمالي ديناميكيًا من البداية.

بعد ذلك، سنقوم بعرض هذا في JSX الخاص بنا:

<div className='total'>الإجمالي: {totalItemCount}</div>

كل شيء سيبدو كما هو حتى الآن. هذا لأننا لم نكتب أي منطق لتحديث الحالة بعد. سننشئ دالة جديدة:

 const calculateTotal = () => {
     const totalItemCount = items.reduce((total, item) => {
         return total + item.quantity;
     }, 0);
     setTotalItemCount(totalItemCount);
 };

تستخدم هذه الدالة reduce لجمع جميع الكميات في مصفوفة items.

أخيرًا، كل ما علينا فعله هو استدعاء هذه الدالة كلما زاد المستخدم/قلل الكمية، أو أضاف عنصرًا جديدًا. قم بتحديث الدوال المعنية على النحو التالي (لقد قمنا بالفعل بإضافتها في الخطوات السابقة):

 const handleAddButtonClick = () => {
     // ...الكود الآخر
     calculateTotal();
 };
 const handleQuantityIncrease = (index) => {
     // ...الكود الآخر
     calculateTotal();
 };
 const handleQuantityDecrease = (index) => {
     // ...الكود الآخر
     calculateTotal();
 };

امض قدمًا وحاول زيادة/تقليل الكميات. ستلاحظ أن الكمية الإجمالية تتغير أيضًا! هذا يضمن أن الإجمالي يعكس دائمًا الحالة الحالية للقائمة.

الخلاصة التقنية

في هذا الدليل، قمنا بتغطية أساسيات بناء تطبيق قائمة تسوق تفاعلي باستخدام React Hooks. لقد تعلمنا كيفية إدارة الحالة (state management) بفعالية باستخدام useState، وكيفية التفاعل مع مدخلات المستخدم وتحديث واجهة المستخدم ديناميكيًا. استخدام دالات مثل map و reduce لتعديل وعرض البيانات بكفاءة هو حجر الزاوية في تطوير تطبيقات React الحديثة. هذا المشروع لا يعزز فهمنا لـ Hooks فحسب، بل يوضح أيضًا أفضل الممارسات في التعامل مع الحالة وتجنب تغييرها مباشرةً (immutability)، مما يؤدي إلى كود أنظف وأكثر قابلية للصيانة. هذه المهارات أساسية لأي مطور React يطمح لبناء تطبيقات قوية وسريعة الاستجابة.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *