شرح React: كيفية التعامل مع عدة مربعات اختيار بطريقة احترافية
مقدمة: لماذا يختلف التعامل مع Checkbox في React؟
يبدو مربع الاختيار في HTML بسيطاً في الظاهر، لكن عند استخدامه داخل React تصبح طريقة إدارته مختلفة، لأن الواجهة هنا تعتمد على الحالة state وليس على التفاعل المباشر مع العنصر فقط. لهذا السبب، فإن التعامل مع عدة مربعات اختيار يتطلب فهماً جيداً لمفهوم Controlled Input وكيفية ربط كل عنصر بقيمة محفوظة داخل التطبيق.
في هذا الدليل ستتعرّف على طريقة احترافية لبناء واجهة تحتوي على عدة خيارات قابلة للتحديد، مع حساب القيمة الإجمالية للعناصر المحددة. كما ستتعلّم استخدام الدوال map() وreduce() وfill() في سيناريو عملي شائع.

ما الذي ستتعلمه في هذا المقال؟
- كيفية استخدام مربع اختيار واحد كعنصر إدخال متحكَّم به في
React. - كيفية إدارة عدة مربعات اختيار عبر مصفوفة داخل
state. - طريقة الاستفادة من
map()لتحديث عنصر واحد داخل المصفوفة. - كيفية استخدام
reduce()لحساب الإجمالي اعتماداً على العناصر المحددة. - إنشاء مصفوفة بطول محدد ومعبأة بقيمة ابتدائية عبر
fill().
الخطوة الأولى: التعامل مع مربع اختيار واحد في React
قبل الانتقال إلى الحالة الأكثر تعقيداً، من الأفضل فهم آلية عمل مربع اختيار واحد. في المثال التالي، يتم تعريف عنصر checkbox بالطريقة التقليدية المشابهة لـ HTML:
<div className="App">
Select your pizza topping:
<div className="topping">
<input type="checkbox" id="topping" name="topping" value="Paneer" />
Paneer
</div>
</div>
في هذا الشكل، يمكنك تحديد المربع أو إلغاء تحديده، لكن التطبيق لا يملك فهماً حقيقياً لحالته ما لم تكن هذه الحالة مرتبطة بـ state.

تحويل مربع الاختيار إلى Controlled Input
في React يُفضَّل أن تكون عناصر الإدخال متحكَّماً بها عبر الحالة. هذا يعني أن قيمة العنصر لا تتغير إلا عندما تتغير قيمة state المرتبطة به.
export default function App() {
const [isChecked, setIsChecked] = useState(false);
const handleOnChange = () => {
setIsChecked(!isChecked);
};
return (
<div className="App">
Select your pizza topping:
<div className="topping">
<input
type="checkbox"
id="topping"
name="topping"
value="Paneer"
checked={isChecked}
onChange={handleOnChange}
/>
Paneer
</div>
<div className="result">
Above checkbox is {isChecked ? "checked" : "un-checked"}.
</div>
</div>
);
}
شرح آلية العمل
تم إنشاء حالة باسم isChecked باستخدام useState() وقيمتها الابتدائية false:
const [isChecked, setIsChecked] = useState(false);
ثم تم تمرير الخاصيتين checked وonChange إلى عنصر الإدخال:
<input ... checked={isChecked} onChange={handleOnChange} />
عند النقر على مربع الاختيار، تُستدعى الدالة handleOnChange وتقوم بعكس القيمة الحالية:
const handleOnChange = () => {
setIsChecked(!isChecked);
};
إذا كانت القيمة true ستتحول إلى false، وإذا كانت false ستتحول إلى true. وبهذه الطريقة يصبح مربع الاختيار مرتبطاً بالكامل بحالة التطبيق.
لماذا يُنصح باستخدام Controlled Input؟
- لأن حالة الإدخال تصبح واضحة ومركزية داخل التطبيق.
- تضمن أن التغيير يحدث من خلال
onChangeفقط. - تمنع التضارب بين واجهة المستخدم والبيانات الداخلية.
- تسهّل تنفيذ المنطق الإضافي مثل التحقق أو الحسابات الفورية.
يمكن استخدام ref للتعامل غير المتحكَّم به في حالات محدودة، لكن النمط المتحكَّم به يظل الخيار الأفضل في أغلب تطبيقات React.
كيفية التعامل مع عدة مربعات اختيار في React
عند الانتقال من مربع واحد إلى عدة مربعات اختيار، تصبح إدارة كل عنصر عبر useState() مستقل أمراً غير عملي. الحل الأفضل هنا هو تخزين حالة جميع المربعات داخل مصفوفة واحدة.

في هذا السيناريو نعرض قائمة إضافات للبيتزا، ولكل إضافة سعر. المطلوب هو حساب المبلغ الإجمالي بناءً على العناصر التي يحددها المستخدم.
إنشاء مصفوفة الحالة باستخدام fill()
لإنشاء مصفوفة بطول يساوي عدد العناصر، يمكن استخدام new Array() مع fill(false):
const [checkedState, setCheckedState] = useState(
new Array(toppings.length).fill(false)
);
إذا كان لدينا 5 عناصر مثلاً، فستكون القيمة الابتدائية كالتالي:
[false, false, false, false, false]
كل قيمة داخل المصفوفة تمثل حالة مربع اختيار واحد. وعند التحديد أو الإلغاء، تتبدل القيمة المقابلة بين true وfalse.
الكود الكامل للتعامل مع مربعات الاختيار المتعددة
import { useState } from "react";
import { toppings } from "./utils/toppings";
import "./styles.css";
const getFormattedPrice = (price) => `$ ${price.toFixed(2)} `;
export default function App() {
const [checkedState, setCheckedState] = useState(
new Array(toppings.length).fill(false)
);
const [total, setTotal] = useState(0);
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
const totalPrice = updatedCheckedState.reduce(
(sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
},
0
);
setTotal(totalPrice);
};
return (
<div className="App">
<h3>Select Toppings</h3>
<ul className="toppings-list">
{toppings.map(({ name, price }, index) => {
return (
<li key={index}>
<div className="toppings-list-item">
<div className="left-section">
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
<label htmlFor={`custom-checkbox-${index}`}>{name}</label>
</div>
<div className="right-section">
{getFormattedPrice(price)}
</div>
</div>
</li>
);
})}
<li>
<div className="toppings-list-item">
<div className="left-section">Total:</div>
<div className="right-section">
{getFormattedPrice(total)}
</div>
</div>
</li>
</ul>
</div>
);
}
شرح الكود خطوة بخطوة
ربط كل مربع اختيار بقيمته داخل المصفوفة
كل عنصر checkbox يحصل على قيمة الخاصية checked من الموضع المقابل له داخل المصفوفة checkedState:
<input
type="checkbox"
id={`custom-checkbox-${index}`}
name={name}
value={name}
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
هذا يعني أن المربع الأول يعتمد على checkedState[0]، والثاني على checkedState[1]، وهكذا.
تمرير الفهرس index عند التغيير
عند تغيير أي مربع، يتم إرسال موقعه إلى الدالة handleOnChange():
onChange={() => handleOnChange(index)}
هذا يسمح لنا بمعرفة أي عنصر يجب تحديثه داخل المصفوفة.
تحديث عنصر واحد باستخدام map()
داخل الدالة handleOnChange() يتم إنشاء مصفوفة جديدة عبر map():
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
المنطق هنا بسيط:
- إذا كان
indexالحالي يساويpositionالمرسل، نعكس القيمة باستخدام!item. - إذا لم يتطابقا، نُبقي القيمة كما هي.
ويمكن كتابة المنطق نفسه بصيغة أكثر تفصيلاً:
const updatedCheckedState = checkedState.map((item, index) => {
if (index === position) {
return !item;
} else {
return item;
}
});
استخدام العامل الثلاثي ?: يجعل الكود أقصر، لكن النتيجة واحدة.
أهمية تحديث state بعد إنشاء المصفوفة الجديدة
بعد تجهيز المصفوفة الجديدة، يجب تمريرها إلى setCheckedState():
setCheckedState(updatedCheckedState);
إذا لم تُحدَّث الحالة، فلن ينعكس التغيير على الواجهة، لأن قيمة checked في كل مربع تعتمد مباشرة على checkedState.
لماذا نستخدم updatedCheckedState بدلاً من checkedState؟
هذه نقطة مهمة جداً. تحديث الحالة في React ليس فورياً دائماً، لأن setState أو setCheckedState() يعملان بشكل غير متزامن في كثير من الحالات. لذلك لا يمكن افتراض أن checkedState سيحمل القيمة الجديدة مباشرة في السطر التالي.
لهذا السبب يتم إنشاء متغير وسيط باسم updatedCheckedState واستخدامه مباشرة في الحسابات، لأنه يحتوي بالفعل على القيم المحدّثة.
حساب الإجمالي باستخدام reduce()
بعد تحديث المصفوفة، يتم حساب مجموع الأسعار للعناصر المحددة عبر reduce():
const totalPrice = updatedCheckedState.reduce(
(sum, currentState, index) => {
if (currentState === true) {
return sum + toppings[index].price;
}
return sum;
},
0
);
تعمل الدالة هنا كالتالي:
- تبدأ القيمة التراكمية
sumمن0. - تمر على كل عنصر داخل
updatedCheckedState. - إذا كانت القيمة الحالية
true، يتم جمع سعر العنصر المناظر منtoppings[index].price. - إذا كانت
false، يستمر المجموع كما هو.
وفي النهاية يتم حفظ النتيجة داخل الحالة total:
setTotal(totalPrice);

أفضل الممارسات عند التعامل مع عدة Checkboxes في React
- استخدم مصفوفة واحدة في
stateعند وجود عناصر متكررة أو قائمة ديناميكية. - تجنب إنشاء
useState()مستقل لكل مربع اختيار إذا كانت القائمة طويلة. - اعتمد على
map()لتوليد الواجهة وتحديث البيانات بطريقة أنظف. - أنشئ نسخة جديدة من المصفوفة بدلاً من تعديلها مباشرة، التزاماً بمبدأ عدم تغيير البيانات الأصلية
immutability. - استخدم
reduce()عندما تحتاج إلى حسابات مشتقة مثل المجموع أو العدد أو القيم المفلترة.
متى تكون هذه الطريقة مناسبة؟
يُعد هذا الأسلوب مناسباً في حالات كثيرة، مثل:
- نماذج اختيار الخدمات أو الإضافات المدفوعة.
- واجهات تصفية المنتجات حسب الخصائص.
- تحديد الصلاحيات في لوحات التحكم.
- اختيار مجموعة عناصر مع حساب إجمالي أو عدد نهائي.
ملاحظات تقنية لتحسين جودة الكود
إذا كنت تبني مشروعاً أكبر، يمكنك لاحقاً تطوير هذا النمط عبر:
- استخدام
useMemoلحساب الإجمالي عند الحاجة إلى تحسين الأداء. - فصل عنصر مربع الاختيار في مكوّن مستقل إذا أصبحت الواجهة كبيرة.
- الاعتماد على معرف فريد بدلاً من
indexإذا كانت القائمة قابلة لإعادة الترتيب أو الحذف.

الخلاصة التقنية
إدارة عدة مربعات اختيار في React تصبح سهلة ومنظمة عندما تتعامل معها كبيانات داخل state بدلاً من اعتبارها عناصر واجهة منفصلة فقط. استخدام مصفوفة من القيم المنطقية مع map() للتحديث وreduce() للحساب يوفّر حلاً واضحاً وقابلاً للتوسع. تقنياً، هذه المقاربة تُعد من أفضل الأساليب لبناء واجهات تفاعلية دقيقة وسهلة الصيانة، خصوصاً في التطبيقات التي تعتمد على القوائم الديناميكية والحسابات الفورية.