State

מהו State?

State הוא נתון פנימי של קומפוננטה שיכול להשתנות לאורך זמן - למשל ספירה שעולה, טקסט שהמשתמש מקליד או חלון שנפתח ונסגר. זהו ההבדל המהותי מ-props: את ה-State הקומפוננטה מנהלת בעצמה. הכוח שלו טמון בכך שבכל פעם שה-State משתנה, React מרנדרת את הקומפוננטה מחדש ומעדכנת את המסך אוטומטית.

import { useState } from 'react';

function Counter() {
    // הגדרת state עם ערך התחלתי 0
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>ספירה: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                הוסף 1
            </button>
        </div>
    );
}

useState Hook

התחביר

const [value, setValue] = useState(initialValue);
//     ↑       ↑                      ↑
//   ערך    פונקציית           ערך התחלתי
//  נוכחי    עדכון

דוגמאות

// מספר
const [count, setCount] = useState(0);

// מחרוזת
const [name, setName] = useState("");

// בוליאני
const [isOpen, setIsOpen] = useState(false);

// מערך
const [items, setItems] = useState([]);

// אובייקט
const [user, setUser] = useState({ name: "", age: 0 });

עדכון State

עדכון ישיר

function Counter() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => setCount(5)}>הגדר ל-5</button>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <button onClick={() => setCount(count - 1)}>-1</button>
        </div>
    );
}

עדכון מבוסס ערך קודם (מומלץ!)

function Counter() {
    const [count, setCount] = useState(0);

    // ✅ מומלץ - שימוש בפונקציית callback
    const increment = () => setCount(prev => prev + 1);
    const decrement = () => setCount(prev => prev - 1);
    const double = () => setCount(prev => prev * 2);

    return (
        <div>
            <p>{count}</p>
            <button onClick={increment}>+1</button>
            <button onClick={decrement}>-1</button>
            <button onClick={double}>×2</button>
        </div>
    );
}

State עם טפסים

function LoginForm() {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log("Email:", email);
        console.log("Password:", password);
    };

    return (
        <form onSubmit={handleSubmit}>
            <input 
                type="email" 
                value={email} 
                onChange={(e) => setEmail(e.target.value)}
                placeholder="אימייל"
            />
            <input 
                type="password" 
                value={password} 
                onChange={(e) => setPassword(e.target.value)}
                placeholder="סיסמה"
            />
            <button type="submit">התחבר</button>
        </form>
    );
}

State עם אובייקטים

function UserForm() {
    const [user, setUser] = useState({
        name: "",
        email: "",
        age: 0
    });

    // ❌ שגוי - לא לשנות ישירות
    // user.name = "דני";

    // ✅ נכון - יצירת אובייקט חדש
    const updateName = (name) => {
        setUser({ ...user, name: name });
    };

    // ✅ או בגרסה מקוצרת
    const updateEmail = (email) => {
        setUser(prev => ({ ...prev, email }));
    };

    return (
        <div>
            <input 
                value={user.name}
                onChange={(e) => updateName(e.target.value)}
            />
            <input 
                value={user.email}
                onChange={(e) => updateEmail(e.target.value)}
            />
        </div>
    );
}

State עם מערכים

function TodoList() {
    const [todos, setTodos] = useState([
        { id: 1, text: "לקנות חלב", done: false },
        { id: 2, text: "לשלם חשבונות", done: false }
    ]);

    // הוספת פריט
    const addTodo = (text) => {
        setTodos(prev => [...prev, {
            id: Date.now(),
            text,
            done: false
        }]);
    };

    // מחיקת פריט
    const deleteTodo = (id) => {
        setTodos(prev => prev.filter(todo => todo.id !== id));
    };

    // עדכון פריט
    const toggleTodo = (id) => {
        setTodos(prev => prev.map(todo => 
            todo.id === id 
                ? { ...todo, done: !todo.done }
                : todo
        ));
    };

    return (
        <ul>
            {todos.map(todo => (
                <li key={todo.id}>
                    <span 
                        style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
                        onClick={() => toggleTodo(todo.id)}
                    >
                        {todo.text}
                    </span>
                    <button onClick={() => deleteTodo(todo.id)}>❌</button>
                </li>
            ))}
        </ul>
    );
}

State vs Props

תכונה Props State
מקור מגיע מההורה פנימי לקומפוננטה
שינוי לא ניתן לשנות ניתן לשנות עם setter
מטרה העברת נתונים ניהול נתונים משתנים
רינדור מחדש כשההורה מתרנדר כש-State משתנה
// Props - נתונים מבחוץ (read-only)
function Greeting({ name }) {
    return <h1>שלום {name}!</h1>;
}

// State - נתונים פנימיים (mutable)
function Counter() {
    const [count, setCount] = useState(0);
    return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

// שילוב: State בהורה, Props לילד
function App() {
    const [name, setName] = useState("דני");
    return (
        <div>
            <input value={name} onChange={(e) => setName(e.target.value)} />
            <Greeting name={name} />  {/* State הופך ל-Props */}
        </div>
    );
}

כללים חשובים

1. אל תשנו State ישירות

// ❌ שגוי
count = count + 1;
user.name = "דני";
todos.push({ id: 1, text: "חדש" });

// ✅ נכון
setCount(count + 1);
setUser({ ...user, name: "דני" });
setTodos([...todos, { id: 1, text: "חדש" }]);

2. State הוא אסינכרוני

קריאה ל-setter אינה מעדכנת את הערך באופן מיידי, אלא מתזמנת רינדור מחדש. לכן מיד אחרי הקריאה המשתנה עדיין מחזיק את הערך הישן. אם צריך לחשב על בסיס הערך העדכני, משתמשים בגרסת ה-callback (prev => ...):

function Example() {
    const [count, setCount] = useState(0);

    const handleClick = () => {
        setCount(count + 1);
        console.log(count); // עדיין הערך הישן!
    };
}

3. מספר useState בקומפוננטה אחת

function Form() {
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");
    const [age, setAge] = useState(0);
    const [isValid, setIsValid] = useState(false);
    // ...
}

סיכום

פעולה קוד
הגדרת State const [x, setX] = useState(initial)
עדכון ישיר setX(newValue)
עדכון מבוסס קודם setX(prev => prev + 1)
עדכון אובייקט setX(prev => ({...prev, key: val}))
הוספה למערך setX(prev => [...prev, newItem])
מחיקה ממערך setX(prev => prev.filter(...))
עדכון במערך setX(prev => prev.map(...))

הקודם: ← Props
הבא: אירועים →

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. למה לא כותבים count = count + 1 ישירות?

    1. זה שגיאת תחביר
    2. זה איטי
    3. זה עובד מצוין
    4. React לא תדע שהערך השתנה ולא תרנדר מחדש
    הצגת התשובה

    תשובה ד. רק קריאה ל-setCount מודיעה ל-React לרנדר מחדש.

  2. איך מעדכנים state לפי הערך הקודם בבטחה?

    1. count++
    2. setCount(count + 1)
    3. setCount(prev => prev + 1)
    4. setCount = count + 1
    הצגת התשובה

    תשובה ג. הצורה הפונקציונלית תמיד מקבלת את הערך העדכני, גם בכמה עדכונים רצופים.

  3. איך מוסיפים איבר למערך ב-state?

    1. items[items.length] = x
    2. setItems([...items, x])
    3. setItems(items.push(x))
    4. items.push(x)
    הצגת התשובה

    תשובה ב. יוצרים מערך חדש. שינוי המערך הקיים לא יגרום לרינדור.