לדלג לתוכן

בנו קלון של Notion ב-React - הפרויקט שילמד אתכם state אמיתי

אפליקציית פתקים נשמעת פשוטה. רשימה, כפתור הוספה, אזור עריכה - מה כבר יכול להיות שם? אבל ברגע שמתחילים לבנות, מתגלה שזה אחד הפרויקטים העשירים ביותר ללמידה. יש שם דפוס UI קלאסי שחוזר בכל אפליקציה אמיתית - רשימה בצד אחד ופרטים בצד השני - ויש שם גם בעיה אמיתית של שמירה אוטומטית בלי לאבד נתונים ובלי להציף את הדפדפן בבקשות מיותרות.

דפוס הרשימה-ופרטים

כמעט כל אפליקציה שאתם מכירים - Gmail, Notion, Trello - בנויה על אותו דפוס בסיסי. יש רשימת פריטים בצד אחד, ולחיצה על פריט מציגה אותו במלואו בצד השני. ברגע שתפנימו את הדפוס הזה תוכלו לזהות אותו בכל אתר שתפתחו. הבנייה שלו ב-React דורשת מקור אמת אחד - state שמחזיק את כל הפתקים - ומשתנה נוסף שמחזיק איזה פתק כרגע נבחר.

const [notes, setNotes] = useState([]);
const [activeId, setActiveId] = useState(null);

const activeNote = notes.find(note => note.id === activeId);

שימו לב לרעיון החשוב כאן - אין state נפרד ל"הפתק הפעיל". יש רק id, וה-note עצמו נגזר מהרשימה בכל רינדור. זה מונע מצב שבו שני מקורות מידע לא מסונכרנים, ואלה בדיוק הבאגים שגורמים למתחילים לשעות של תסכול.

מבנה הקומפוננטות

חלקו את האפליקציה לשלושה חלקים ברורים - רכיב רשימה שמציג את כל הפתקים ומאפשר לבחור אחד, רכיב עורך שמציג את הפתק הנבחר ומאפשר לערוך כותרת ותוכן, ורכיב הורה שמחזיק את ה-state ומעביר אותו למטה דרך props. זה תרגול מצוין בהעברת נתונים ופונקציות בין קומפוננטות, מה שנקרא בעגה המקצועית "הרמת state" - lifting state up.

כתיבה, עריכה ומחיקה

הוספת פתק חדש היא פעולה שיוצרת אובייקט עם id ייחודי (אפשר להשתמש ב-crypto.randomUUID) ומוסיפה אותו למערך. עריכה היא הצד המורכב יותר - כל הקלדה במשתמש צריכה לעדכן את הפתק הספציפי בתוך המערך, בלי לגעת בפתקים האחרים. מחיקה היא סינון המערך והחזרת כל מה שלא הפתק שנמחק. שלוש הפעולות האלה - יצירה, עדכון ומחיקה בתוך מערך ב-state - הן בדיוק המיומנות שנבחנת בכל ראיון עבודה על React.

שמירה אוטומטית בלי לשבור את הביצועים

הרעיון של autosave נשמע פשוט - כל שינוי נשמר. אבל אם תשמרו ל-localStorage בכל הקשה על מקלדת, תגרמו לפגיעה בביצועים בלי סיבה טובה. הפתרון הנפוץ הוא debounce - ממתינים כמה מאות מילישניות אחרי שהמשתמש הפסיק להקליד, ורק אז שומרים. זה עוד מושג שכדאי להכיר טוב, כי הוא חוזר בהמון תרחישים - חיפוש חי, שמירה אוטומטית, ואפילו קריאות API בזמן הקלדה.

useEffect(() => {
  const timer = setTimeout(() => {
    localStorage.setItem('notes', JSON.stringify(notes));
  }, 500);
  return () => clearTimeout(timer);
}, [notes]);

אם תרצו להעמיק למה ה-useEffect הזה מנקה את עצמו בכל הרצה מחדש, ולמה זה חיוני כדי ש-debounce יעבוד נכון, זה בדיוק סוג הפרטים שמוסברים בקורס צד לקוח שלנו, לצד עוד המון דוגמאות מהחיים האמיתיים.

המלכודות הנפוצות

הטעות השכיחה ביותר היא לעדכן אובייקט בתוך מערך בצורה ישירה (mutation) במקום ליצור מערך חדש - זה שובר את היכולת של React לזהות שינוי ולרנדר מחדש. טעות שנייה היא לשכוח מסך ריק כשאין אף פתק נבחר. וטעות שלישית - להתעלם ממצב שבו המשתמש מוחק את הפתק שכרגע פתוח, מה שדורש איפוס ה-id הפעיל.

מה מקבלים בסוף

בסיום הפרויקט תבינו לעומק את דפוס הרשימה-ופרטים, תדעו לנהל מערכים ב-state בלי לשבור אימיוטביליות, ותכירו debounce - שלושה מושגים שמלווים כמעט כל אפליקציית React אמיתית. זה פרויקט קטן בהיקף אבל עמוק בלמידה.

אם עדיין לא עברתם את היסודות של React, הקורס צד לקוח באתר בחינם ובעברית, ובנוי בדיוק כדי להגיע לפרויקטים כאלה בביטחון.

הצטרפו לקהילה בדיסקורד