← الدرس السابق المستوى 4 - الدرس 5 من 12

🚀 مشروع React عملي: بناء تطبيق إدارة المهام (Todo App) خطوة بخطوة

تقدمك في المستوى الرابع 41%

📋 مقدمة: ليه تطبيق المهام هو أفضل مشروع تبدأ بيه؟

تطبيق إدارة المهام (Todo App) هو المشروع الأكثر شيوعاً بين مطوري React المبتدئين، وده مش بسبب بساطته فقط، لكن لأنه يغطي 90% من المفاهيم الأساسية اللي هتحتاجها في أي تطبيق React حقيقي. في هذا الدرس، هنبني تطبيق مهام كامل من الصفر باستخدام Components, Props, State, useEffect، وكمان هنحفظ البيانات في المتصفح عشان ماتضيعش لما تغلق الصفحة.

بنهاية هذا الدرس، هتكون قادر على:

💡 نصيحة: افتح محرر الأكواد (VS Code) وطبق كل خطوة مع الدرس. التطبيق العملي هو السر الوحيد لتعلم React بشكل فعلي.

📁 هيكل المشروع الكامل

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

src/ ├── components/ │ ├── TodoForm.js // نموذج إضافة مهمة جديدة مع التحقق من المدخلات │ ├── TodoList.js // قائمة عرض جميع المهام مع التصفية │ ├── TodoItem.js // عنصر مهمة واحدة مع Checkbox وحذف │ └── TodoStats.js // إحصائيات المهام (المنجزة والمتبقية) ├── App.js // المكون الرئيسي - يدير State ويوزع البيانات ├── App.css // التنسيقات └── index.js // نقطة الدخول

1️⃣ App.js - المكون الرئيسي وإدارة الحالة

المكون App.js هو "عقل" التطبيق. هنا بنخزن كل المهام في useState، ونعمل الدوال اللي بتضيف وتمسح وتعدل المهام. الميزة المهمة هنا إننا بنستخدم Initializer Function في useState عشان نقرأ البيانات من localStorage لما التطبيق يفتح لأول مرة.

🔍 شرح الكود:

import { useState, useEffect } from 'react'; import TodoForm from './components/TodoForm'; import TodoList from './components/TodoList'; import TodoStats from './components/TodoStats'; import './App.css'; function App() { // ✅ Initializer Function: تقرأ localStorage لما التطبيق يفتح فقط const [todos, setTodos] = useState(() => { const saved = localStorage.getItem('todos'); return saved ? JSON.parse(saved) : []; }); // ✅ useEffect: يحفظ المهام في localStorage عند كل تغيير useEffect(() => { localStorage.setItem('todos', JSON.stringify(todos)); }, [todos]); // ➕ إضافة مهمة جديدة const addTodo = (text) => { if (!text.trim()) return; // التحقق من المدخلات الفارغة const newTodo = { id: Date.now(), // طابع زمني فريد text: text, completed: false, createdAt: new Date() }; setTodos([newTodo, ...todos]); // إضافة في البداية }; // ✅ تبديل حالة المهمة (منجزة / غير منجزة) const toggleTodo = (id) => { setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo )); }; // 🗑️ حذف مهمة واحدة const deleteTodo = (id) => { setTodos(todos.filter(todo => todo.id !== id)); }; // 🧹 حذف كل المهام المنجزة const clearCompleted = () => { setTodos(todos.filter(todo => !todo.completed)); }; return ( <div className="app-container"> <header> <h1>✅ تطبيق إدارة المهام</h1> <p>نظم مهامك اليومية بكفاءة</p> </header> <main> <TodoStats todos={todos} /> <TodoForm onAdd={addTodo} /> <TodoList todos={todos} onToggle={toggleTodo} onDelete={deleteTodo} /> {todos.some(t => t.completed) && ( <button className="clear-btn" onClick={clearCompleted}> 🧹 حذف المهام المنجزة </button> )} </main> </div> ); } export default App;

2️⃣ TodoForm.js - نموذج إضافة مهمة مع التحقق

المكون TodoForm مسؤول عن استقبال المدخلات من المستخدم. الميزة المهمة هنا إننا بنستخدم Controlled Component (القيمة مربوطة بـ State)، وكمان بنمنع إرسال المهام الفارغة باستخدام trim().

import { useState } from 'react'; function TodoForm({ onAdd }) { const [text, setText] = useState(''); const handleSubmit = (e) => { e.preventDefault(); // منع إعادة تحميل الصفحة onAdd(text); setText(''); // تفريغ الحقل بعد الإضافة }; return ( <form onSubmit={handleSubmit} className="todo-form"> <input type="text" value={text} onChange={(e) => setText(e.target.value)} placeholder="أضف مهمة جديدة..." className="todo-input" /> <button type="submit" className="btn-add"> ➕ إضافة </button> </form> ); } export default TodoForm;

3️⃣ TodoItem.js - عرض مهمة واحدة مع التفاعل

كل مهمة عبارة عن مكون مستقل يستقبل البيانات عبر Props ويرسل الأحداث للأب عبر دوال الـ Callback. بنستخدم Conditional CSS Classes عشان نغير شكل المهمة المنجزة.

function TodoItem({ todo, onToggle, onDelete }) { return ( <li className={`todo-item ${todo.completed ? 'completed' : ''}`}> <input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} className="checkbox" /> <span className="todo-text">{todo.text}</span> <button className="btn-delete" onClick={() => onDelete(todo.id)} aria-label="حذف المهمة" > 🗑️ </button> </li> ); } export default TodoItem;

4️⃣ TodoStats.js - إحصائيات المهام في الوقت الفعلي

المكون ده بيحسب الإحصائيات بشكل ديناميكي كل مرة المهام تتغير. بنستخدم filter عشان نعد المهام المنجزة، وبنستخدم useMemo لو كنت عايز تحسين الأداء مع عدد كبير من المهام.

function TodoStats({ todos }) { const total = todos.length; const completed = todos.filter(t => t.completed).length; const remaining = total - completed; return ( <div className="stats"> <p>📊 المجموع: {total}</p> <p>✅ منجزة: {completed}</p> <p>⏳ متبقية: {remaining}</p> </div> ); } export default TodoStats;

🎯 ما تعلمته من هذا المشروع

بعد بناء هذا المشروع، أصبحت تفهم المفاهيم التالية بشكل عملي:

⚠️ أخطاء شائعة وتجنبها

❌ الخطأ 1: تعديل State مباشرة

غلط: todos.push(newTodo) - ده بيعدل المصفوفة الأصلية وReact مش هتشوف التغيير.

صح: setTodos([...todos, newTodo]) - بنعمل نسخة جديدة من المصفوفة.

❌ الخطأ 2: نسيان Dependency Array في useEffect

غلط: useEffect(() => { ... }) - ده بيشتغل مع كل render ويسبب loop لانهائي.

صح: useEffect(() => { ... }, [todos]) - بنحدد متى يشتغل.

❌ الخطأ 3: استخدام index كـ Key

غلط: <TodoItem key={index} /> - لو حذفت عنصر، الـ index بتتغير وReact بيحصل confusion.

صح: <TodoItem key={todo.id} /> - استخدم ID فريد ثابت.

💡 التحسينات المستقبلية

بعد ما تتقن المشروع الأساسي، جرب تضيف الميزات دي عشان ترفع مستواك:

📚 دروس ذات صلة

❓ أسئلة شائعة

هل يمكن استخدام useReducer بدلاً من useState في هذا المشروع؟

نعم، وده يُفضل فعلاً لو التطبيق هيكبر. useReducer مناسب أكتر لما يكون عندك حالة معقدة (زي إضافة + حذف + تعديل + تصفية). لكن في مشروع Todo App البسيط، useState أسهل وأوضح للمبتدئين.

هل localStorage آمن لحفظ البيانات الحساسة؟

لا، localStorage غير آمن للبيانات الحساسة (كلمات مرور، بيانات بنكية). أي شخص يقدر يفتح DevTools ويقرأ المحتوى. استخدمه للبيانات العامة بس زي قائمة المهام. للبيانات الحساسة، استخدم Backend مع تشفير.

إزاي أحول المشروع ده لتطبيق PWA (تطبيق ويب تقدر تثبته على الموبايل)؟

سهل! أضف ملف manifest.json و service-worker.js. React بيدعم PWA من الصندوق - استخدم npx create-react-app my-app --template cra-template-pwa في المشاريع الجديدة.

← الدرس السابق