سأل سائل على سطاك أوفر فلو عن الطريقة المتوافقة مع فلسفة رياكت (هل نسميها الطريقة الرياكتية؟) لمعالجة الاستمارات الكبيرة، أي الوصول إلى قيمها وحفظها وتعديلها. بعيدًا عن الطريقة التقليدية التي تتمثل في الوصول إلى العنصر باستخدام معرّفه بالوظيفة getElementById، إذ صار معلومًا أنّ هذا الأسلوب لا يتوافق مع مبادئ رياكت أبدًا وغير محبذ ولو كان يشتغل. فجاءت أجوبة سائلنا متعددة متنوّعة، لكن كلّ جواب يحمل في طياته جانبًا كريها يجعلني أستبعده لأفكر أخيرًا في أفضل طريقة و”أنقاها” لمعالجة الاستمارات صغيرة كانت أم كبيرة.

السؤال

على فرض أنّ عندي استمارة من 30 حقل إدخال (input field)، فكيف يمكنني الوصول إلى قيَم هذه المدخلات في رياكت. وذلك علمًا أن رياكت يعتمد فلسفة المكوّن والحالة (Component & State) وأن مجرى البيانات Dataflow الطبيعي هو إعادة تصيير المكوّن إمّا بمعطيات جديدة أو بتغيير حالته، وليس بالوصول إلى دوم DOM الكائن نفسه وجلب وتعديل بياناته. فكيف السبيل وأين المقدرة؟

الجواب

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

المكوّن في رياكت لا يتغيّر إلا بتغيّر حالته، حتى ولو كان عنصر أشتمل عادي مثل حقل إدخال، فعنصرٌ كهذا عهدناه في السابق يغيّر قيمته بشكل تلقائي بإدخال نصوص عبر أزرار لوحة المفاتيح، ونحصل على هذه القيمة بالوصول إلى خاصيته value. لكن في رياكت فإن حقل الإدخال مكوِن له حالة وهي النص المعروض، تتغيّر هذه الحالة عندما يُدخل المستخدم نصًا باستخدام الأزرار (أو غيرها طبعًا)، في كلّ مرّة تتغير هذه الحالة (أي في كلّ كبسة زر)، فإن الكائن يعاد تصييره ليعرض الحالة الجديدة، أي ليعرض القيمة الجديدة. باعتبار هذه الرؤية الجديدة، فإن كلّ حقل من حقول الواجهة يجب أن تُرفع حالته Lift State إلى المكوّن الأب هو مكوّن الواجهة نفسها، وحالته هي قيمته وبالتالي فإن مكوّن الواجهة يجب أن يضم 30 حالة تمثل قيَم حقول الإدخال. هذا مثال لتفهم أكثر:

class MyFormComponent extends React.Component {
	
	constructor(props) {
		super(props);
		this.state = {
			inputValue1: '',
			inputValue2: '',
			inputValue3: '',
			...
		}
	}
		
	render() {
		return (
			//...
			<input 
				value={this.state.inputValue1} 
				onChange={evt => this.setState({inputValue1: evt.targer.value})}
			/>
			<input 
				value={this.state.inputValue1} 
				onChange={evt => this.setState({inputValue2: evt.targer.value})}
			/>
			<input 
				value={this.state.inputValue1} 
				onChange={evt => this.setState({inputValue3: evt.targer.value})}
			/>
			//...
		);
	}
	
}

قد تبدو لك للوهلة الأولى أن هذه الطريقة جيدة ومقبولة ما دامت تتوافق مع فلسفة رياكت، لكن ماذا لو أردت (والراجح أنك ستفعل) أن تعالج القيمة قبل إسنادها للحالة، هنا سيلزمك وضع وظيفة معالجة لكل مكوّن إدخال مثل: handleInput1، handleInput2، لكن سرعان ما يصبح الأمر جنونيًا عندما ترى نفسك تضع 30 وظيفة فقط لمعالجة قيَم الواجهة بعد أن وضعت مسبقًا 30 حالة قيمة. الأمر لم يعد ممتعًا مع رياكت أليس كذلك؟

الحل المقترح هنا هو تعديل ذكي لما سبق، ستقوم بحذف الحالات الـ 30 وتضع مكانها كائن حالة سمّه أي شيء مثل inputs كالتالي.

class MyFormComponent extends React.Component {
	
	constructor(props) {
		super(props);
		this.state = {
			inputs : {}
		}
	}

}

سيمثل الكائن inputs جميع عناصر الواجهة وسيتم حفظها على الشكل التالي:

inputs : {
	inputName1: inputValue1,
	inputName2: inputValue2,
	//...
}

من أجل ذلك سننشئ وظيفة واحدة على حدث التغيير onChange تقوم بإدراج اسم العنصر وقيمته في inputs كالتالي:

handleChange = (event) => {
	this.setState({ inputs: { ...this.state.inputs, ...{[event.target.name]: event.target.value} } });
}

هذا كل شيء، عند كل تغيير تتم إعادة تصيير الواجهة وصياغتها على شكل كائن جيزون ضمن الحالة inputs جاهز للمعالجة والإرسال عبر النت إلى الخادوم. هذا الرِماز (الكود) الكامل:

class MyFormComponent extends React.Component {
	
	constructor(props) {
		super(props);
		this.state = {
			inputs : {}
		}
	}
	
	handleChange = (event) => {
		this.setState({ inputs: { ...this.state.inputs, ...{[event.target.name]: event.target.value} } });
	}
		
	render() {
		return (
			//...
			<input 
				name="inputName1"
				value={this.state.inputValue1} 
				onChange={event => this.handleChange(event)}
			/>
			<input 
				name="inputName2"
				value={this.state.inputValue1} 
				onChange={event => this.handleChange(event)}
			/>
			<input 
				name="inputName3"
				value={this.state.inputValue1} 
				onChange={event => this.handleChange(event)}
			/>
			//...
		);
	}
	
}

خاتمة

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