تحديث حالة المكون الأب من المكون الابن عند النقر في React: دليل شامل

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

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

لنبدأ بالنظر إلى جزء من التعليمات البرمجية الأولية التي قد تكون كتبتها لتطبيق صندوق الوصفات:

 import React from "react" ; import ReactDOM from "react-dom" ; import RecipeBox from "./RecipeBox" ; const rootElement = document .getElementById( "root" ); ReactDOM.render( < React.StrictMode > < RecipeBox /> </ React.StrictMode > , rootElement ); 
 import React from "react" ; export default class RecipeBox extends React . Component { constructor (props) { super (props); this .state = { input : "" , recipeList : [ { recipe : "Tacos" , directions : "make tacos" , ingredients : [ "meat" ] }, { recipe : "pizza" , directions : "bake" , ingredients : [ "dough" ] } ] }; this .handleChange = this .handleChange.bind( this ); this .handleSubmit = this .handleSubmit.bind( this ); } handleChange(event) { this .setState({ input : event.target.value }); } handleSubmit() { const newRecipe = this .state.recipelist[ 0 ].recipe; setState({ recipeList[ 0 ].recipe: newRecipe }); } render() { const ITEMS = this .state.recipeList.map( ( { directions } ) => ( < li > {directions} </ li > )); return ( < div > < EditList input = {this.state.input} handleChange = {this.handleChange} onSubmit = {this.handleSubmit} /> < ul > {ITEMS} </ ul > </ div > ); } } class EditList extends React . Component { render() { return ( < div > < input type = 'text' value = {this.props.input} onChange = {this.props.handleChange} /> < button onClick = {this.props.onSubmit} > Submit </ button > </ div > ); } } 

في هذا المثال، تهدف إلى أن تقوم الدالة handleChange() بالتقاط ما يدخله المستخدم في حقل النص وتحديث وصفات معينة. ولكن عند محاولة تشغيل التطبيق، ستلاحظ ظهور العديد من الأخطاء في الطرفية (terminal) ووحدة تحكم المطور (dev console). دعنا نلقي نظرة فاحصة على ما يحدث وكيفية إصلاحه.

فهم الأخطاء الشائعة وإصلاحها

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

1. تصحيح استدعاء `setState`

الخطأ الأول يكمن في الدالة handleSubmit(). عند تحديث الحالة في مكونات الفئة (Class Components) في React، يجب دائمًا استدعاء الدالة setState من خلال الكائن this الخاص بالمكون. أي أنك تحتاج إلى استخدام this.setState() بدلاً من مجرد setState().

التصحيح سيكون كالتالي:

 handleSubmit() { const newRecipe = this .state.recipelist[ 0 ].recipe; this .setState({ recipeList[ 0 ].recipe: newRecipe }); } 

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

2. التعامل مع تحديث المصفوفات المتداخلة بشكل آمن

المشكلة الثانية والأكثر أهمية هي محاولتك لتحديث حالة مصفوفة متداخلة مباشرة (recipeList[0].recipe: newRecipe). في React، من الممارسات الشائعة والموصى بها بشدة عدم تعديل كائنات الحالة (state objects) أو المصفوفات (arrays) مباشرة. هذا المبدأ يُعرف بـ “عدم القابلية للتغيير” (Immutability). عند تعديل الحالة مباشرة، لا يستطيع React اكتشاف التغييرات بشكل فعال، مما قد يؤدي إلى مشاكل في الأداء أو عدم تحديث واجهة المستخدم (UI) كما هو متوقع.

بدلاً من ذلك، تحتاج إلى إنشاء نسخة جديدة من المصفوفة الكاملة recipeList، ثم تعديل قيمة الخاصية recipe للعنصر الذي تريد تحديثه في المصفوفة المنسوخة، وأخيرًا تعيين المصفوفة المعدلة الجديدة مرة أخرى إلى this.state.recipeList.

الخطوة 1: إنشاء نسخة من المصفوفة باستخدام معامل النشر (Spread Syntax)

أولاً، استخدم معامل النشر (spread syntax) لإنشاء نسخة ضحلة (shallow copy) من مصفوفة this.state.recipeList. هذا يضمن أنك تعمل على نسخة منفصلة ولا تعدل الحالة الأصلية مباشرة.

 handleSubmit() { const recipeList = [...this.state.recipeList]; // ... بقية التعليمات البرمجية } 

الخطوة 2: تعديل العنصر المطلوب في النسخة

بعد إنشاء النسخة، يمكنك الآن تعديل العنصر المحدد داخل المصفوفة recipeList الجديدة. لنفترض أننا نريد تحديث العنصر الأول كإثبات للمفهوم. سنقوم بتعيين قيمة حقل الإدخال (this.state.input) إلى خاصية recipe للعنصر الأول.

 handleSubmit() { const recipeList = [...this.state.recipeList]; recipeList[ 0 ].recipe = this .state.input; // ... بقية التعليمات البرمجية } 

الخطوة 3: تحديث الحالة بالنسخة الجديدة ومسح حقل الإدخال

أخيرًا، قم بتحديث الحالة الحالية recipeList بالنسخة الجديدة المعدلة. من الجيد أيضًا تعيين input إلى سلسلة نصية فارغة ("") بحيث يتم إفراغ مربع النص بعد أن ينقر المستخدم على زر “إرسال” (Submit).

 handleSubmit() { const recipeList = [...this.state.recipeList]; recipeList[ 0 ].recipe = this .state.input; this .setState({ recipeList, input : "" }); } 

الآن، عند تشغيل التطبيق، ستجد أن الحالة يتم تحديثها بشكل صحيح، وأن المكون الأب يستجيب للتغييرات التي تتم في المكون الابن دون أي أخطاء.

لقد قمت بالفعل بتمرير الخصائص (props drilling) أو تمرير البيانات من المكون الأب RecipeBox إلى المكون الابن EditList بشكل صحيح. إن فهم كيفية تحديث الحالة بشكل آمن هو المفتاح لبناء تطبيقات React قابلة للصيانة وفعالة.

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

تُظهر هذه المشكلة الشائعة في React أهمية فهم مبادئ إدارة الحالة، وخاصة مفهوم عدم القابلية للتغيير (Immutability). إن محاولة تعديل الحالة مباشرة، لا سيما الكائنات أو المصفوفات المتداخلة، يمكن أن يؤدي إلى سلوك غير متوقع وصعوبة في تتبع الأخطاء. الحل يكمن في إنشاء نسخ جديدة من أجزاء الحالة التي تحتاج إلى تعديلها، ثم استخدام this.setState() لتحديث الحالة بالنسخ الجديدة. هذه الممارسة لا تضمن فقط تحديث واجهة المستخدم بشكل صحيح، بل تحسن أيضًا أداء التطبيق وتسهل عملية تصحيح الأخطاء. تذكر دائمًا أن الحالة في React يجب أن تُعامل ككائنات غير قابلة للتغيير لضمان تدفق بيانات موثوق ومتوقع.

اترك تعليقاً

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