לדלג לתוכן

6.5 אירועים ורינדור מותנה הרצאה

אירועים בריאקט - Events

ריאקט מטפלת באירועים בצורה דומה ל-DOM, אבל עם כמה הבדלים:

  • שמות אירועים ב-camelCase: onClick, onChange, onSubmit (לא onclick, onchange)
  • מעבירים פונקציה כ-handler, לא מחרוזת: onClick={handleClick} (לא onclick="handleClick()")
  • האירוע שמתקבל הוא SyntheticEvent של ריאקט - עוטף את האירוע המקורי של הדפדפן
function Button() {
    const handleClick = () => {
        console.log("Button clicked!");
    };

    return <button onClick={handleClick}>Click me</button>;
}

פונקציה inline לעומת handler נפרד

function Example() {
    // separate handler - cleaner for complex logic
    const handleClick = () => {
        console.log("clicked");
    };

    return (
        <div>
            <button onClick={handleClick}>Separate handler</button>

            {/* inline - fine for simple, one-line handlers */}
            <button onClick={() => console.log("clicked")}>Inline handler</button>
        </div>
    );
}

העברת ארגומנטים ל-handler

כשצריך להעביר ארגומנט ל-handler, עוטפים אותו בפונקציית חץ:

function ItemList() {
    const items = ["apple", "banana", "cherry"];

    const handleItemClick = (item: string) => {
        console.log("Selected:", item);
    };

    return (
        <ul>
            {items.map((item) => (
                <li key={item}>
                    {/* wrap in arrow function to pass the argument */}
                    <button onClick={() => handleItemClick(item)}>{item}</button>
                </li>
            ))}
        </ul>
    );
}

טעות נפוצה - קריאה לפונקציה במקום העברתה:

// WRONG - calls handleClick immediately during render!
<button onClick={handleClick("hello")}>Click</button>

// CORRECT - passes a function that will call handleClick when clicked
<button onClick={() => handleClick("hello")}>Click</button>

אובייקט האירוע - Event Object

ה-handler מקבל אובייקט אירוע עם מידע שימושי:

function Form() {
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault(); // prevent page reload
        console.log("Form submitted");
    };

    const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
        console.log("Click position:", e.clientX, e.clientY);
        console.log("Target element:", e.target);
        console.log("Current target:", e.currentTarget);
    };

    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
        console.log("Key pressed:", e.key);
        if (e.key === "Enter") {
            console.log("Enter key pressed");
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <input onKeyDown={handleKeyDown} placeholder="Type here..." />
            <button onClick={handleClick}>Submit</button>
        </form>
    );
}

אירועים נפוצים

אירוע מתי מופעל
onClick לחיצה
onChange שינוי ערך (input, select, textarea)
onSubmit שליחת טופס
onKeyDown / onKeyUp לחיצה/שחרור מקש
onFocus / onBlur כניסה/יציאה מפוקוס
onMouseEnter / onMouseLeave כניסה/יציאה של העכבר
onDoubleClick לחיצה כפולה

מניעת התנהגות ברירת מחדל - preventDefault

כמו ב-vanilla JS, preventDefault() מונע את ההתנהגות הרגילה:

function Link() {
    const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
        e.preventDefault(); // prevent navigation
        console.log("Link clicked, but not navigating");
    };

    return <a href="https://example.com" onClick={handleClick}>Click me</a>;
}

רינדור מותנה - Conditional Rendering

ריאקט מאפשרת להציג תוכן שונה לפי תנאים. יש כמה דרכים לעשות זאת.

אופרטור AND - &&

מציג את הצד הימני רק אם הצד השמאלי הוא truthy:

function Notification({ count }: { count: number }) {
    return (
        <div>
            <h1>Dashboard</h1>
            {count > 0 && <p>You have {count} new notifications</p>}
        </div>
    );
}

כשה-count הוא 0 או שלילי, הפסקה לא מוצגת.

מלכודת ה-0 עם &&

זהירות! כש-0 נמצא בצד השמאלי של &&, ריאקט מציגה "0" במקום כלום:

// BUG: when count is 0, renders "0" on screen
{count && <p>You have {count} notifications</p>}

// FIX: explicitly convert to boolean
{count > 0 && <p>You have {count} notifications</p>}

// OR use Boolean()
{Boolean(count) && <p>You have {count} notifications</p>}

הסיבה: 0 && <p>...</p> מחזיר 0 (לא false), וריאקט מרנדרת 0 כטקסט. false && <p>...</p> מחזיר false שריאקט מתעלמת ממנו.

אופרטור טרנרי - Ternary

מציג תוכן אחד אם התנאי אמת, ואחר אם שקר:

function LoginStatus({ isLoggedIn }: { isLoggedIn: boolean }) {
    return (
        <div>
            {isLoggedIn ? (
                <p>Welcome back!</p>
            ) : (
                <p>Please log in.</p>
            )}
        </div>
    );
}

טרנרי טוב למקרים פשוטים של "או זה או זה". למקרים מורכבים יותר, עדיף דרכים אחרות.

החזרה מוקדמת - Early Return

כשיש תנאי פשוט שמשנה את כל ה-UI, אפשר להחזיר JSX שונה מוקדם:

function UserProfile({ user }: { user: User | null }) {
    if (!user) {
        return <p>Loading...</p>;
    }

    return (
        <div>
            <h1>{user.name}</h1>
            <p>{user.email}</p>
        </div>
    );
}

זה שימושי במיוחד למצבי טעינה (loading), שגיאות (error), וחוסר נתונים (empty state).

משתנה JSX

אפשר לחשב את ה-JSX לפני ה-return ולשמור אותו במשתנה:

function StatusBadge({ status }: { status: "active" | "inactive" | "pending" }) {
    let badge: React.ReactNode;

    if (status === "active") {
        badge = <span style={{ color: "green" }}>Active</span>;
    } else if (status === "inactive") {
        badge = <span style={{ color: "red" }}>Inactive</span>;
    } else {
        badge = <span style={{ color: "orange" }}>Pending</span>;
    }

    return <div>Status: {badge}</div>;
}

זה טוב כשיש יותר משני מצבים - ternary מקונן הופך ללא קריא.

map של תנאים למחלקות CSS

function Alert({ type }: { type: "info" | "warning" | "error" }) {
    const classNames: Record<string, string> = {
        info: "alert-info",
        warning: "alert-warning",
        error: "alert-error",
    };

    return <div className={classNames[type]}>Alert content</div>;
}

שילוב - אירועים עם רינדור מותנה

דוגמה שמשלבת אירועים ורינדור מותנה:

import { useState } from "react";

function Accordion({ title, children }: { title: string; children: React.ReactNode }) {
    const [isOpen, setIsOpen] = useState(false);

    return (
        <div style={{ border: "1px solid #ddd", borderRadius: "8px", marginBottom: "8px" }}>
            <button
                onClick={() => setIsOpen(!isOpen)}
                style={{ width: "100%", padding: "12px", textAlign: "left", cursor: "pointer" }}
            >
                {title} {isOpen ? "[-]" : "[+]"}
            </button>
            {isOpen && (
                <div style={{ padding: "12px" }}>{children}</div>
            )}
        </div>
    );
}

function App() {
    return (
        <div>
            <Accordion title="Section 1">
                <p>Content of section 1.</p>
            </Accordion>
            <Accordion title="Section 2">
                <p>Content of section 2.</p>
            </Accordion>
        </div>
    );
}

סיכום

  • אירועים בריאקט משתמשים ב-camelCase ומקבלים פונקציה (לא מחרוזת)
  • מעבירים ארגומנטים ל-handler עם פונקציית חץ: onClick={() => fn(arg)}
  • e.preventDefault() מונע התנהגות ברירת מחדל
  • רינדור מותנה עם &&: הצגה רק אם תנאי מתקיים (זהירות מ-0)
  • רינדור מותנה עם ternary: או זה או זה
  • early return: החזרת JSX שונה בתחילת הפונקציה
  • משתנה JSX: חישוב התוכן לפני ה-return עם if/else