function TodoList() {
const [todos, setTodos] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false; // ← Race Condition protection
async function loadTodos() {
try {
const res = await fetch('/api/todos');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
if (!cancelled) setTodos(data); // ← בדוק לפני setState
} catch (err) {
if (!cancelled) setError(err.message);
} finally {
if (!cancelled) setLoading(false);
}
}
loadTodos();
return () => { cancelled = true; }; // ← Cleanup!
}, []);
if (loading) return <p>טוען...</p>;
if (error) return <p>שגיאה: {error}</p>;
return <ul>{todos.map(t => <li key={t.id}>{t.text}</li>)}</ul>;
}
💡 למה cancelled flag?
משתמש עובר לדף אחר לפני שה-fetch סיים. ללא cancelled: setTodos() יקרא על קומפוננטה שכבר לא על המסך → React Warning + Memory Leak. Cleanup function מציבה cancelled=true.
✏תרגול — Fetch Pattern4 שאלות
שאלה 1 — fetch + 404
למה הקוד הזה לא יעבוד נכון? const res = await fetch('/api/todos/999');
const data = await res.json();
setTodos(data);
✓ תשובה: ב) — fetch לא זורק על 4xx/5xx! תיקון: if (!res.ok) throw new Error(res.status) לפני res.json().
שאלה 2 — async useEffect
מה שגוי בקוד זה (3 שגיאות)? useEffect(async () => {
const res = await fetch(`/api/users?q=${query}`);
setUsers(await res.json());
}); // ← שים לב
✓ תשובה: ב) 3 שגיאות: (1) async ישירות בuseEffect שגוי. (2) אין dependency array → infinite loop. (3) אין if(!res.ok).
שאלה 3 — Race Condition
משתמש לוחץ מהר על "User 1" ואז "User 2". מה יקרה ללא cancelled flag?
✓ תשובה: ב) — Race Condition: בקשה ל-User2 יצאה, אבל בקשת User1 שהייתה איטית חוזרת אחרי ומדרסת. cancelled flag: כשiD משתנה, cleanup מציבה cancelled=true על הבקשה הישנה.
שאלה 4 — POST headers
שלחנו POST אבל השרת מחזיר 400. מה הגורם הנפוץ?
✓ תשובה: א) — ללא Content-Type: application/json, השרת לא יודע שה-body הוא JSON → 400. זה גם הגורם הנפוץ ביותר לבאגים בPOST requests!
// פרקים 3–4
Axios, Loading States ו-Optimistic Updates
Axios עוטפת fetch ומוסיפה: auto-JSON, שגיאה על 4xx/5xx, Interceptors לToken. Loading/Error States = UX נכון.
Fetch vs Axios
מה רוצים
Fetch (מובנה)
Axios (ספרייה)
שגיאות על 4xx/5xx
ידני: if(!res.ok)
אוטומטי — throw!
קריאת JSON
await res.json() ידני
response.data ישירות
שליחת JSON
JSON.stringify + Content-Type
אוטומטי
Token גלובלי
בכל בקשה בנפרד
Interceptor — פעם אחת!
Timeout
צריך AbortController
timeout: 5000 בהגדרה
// api.js — Instance עם הגדרות קבועות:
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.myapp.com',
timeout: 5000,
});
// Interceptor: הוסף Token לכל בקשה אוטומטית:
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
export default api;
// שימוש:
const { data } = await api.get('/todos'); // data מגיע ישירות!
const { data: created } = await api.post('/todos', { text, done: false });
// שגיאה נזרקת אוטומטית:
try { await api.get('/todos/999'); }
catch (err) { console.log(err.response.status); } // 404
Loading/Error States + Optimistic Update
// תמיד שלושה:
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// סדר הבדיקות חשוב:
if (loading) return <div>טוען...</div>;
if (error) return <div>שגיאה: {error}</div>;
if (!data) return null; // הגנה
return <div>{data.name}</div>; // data בטוח לא null
// Optimistic Update — UI קודם, Rollback אם נכשל:
async function toggleTodo(id) {
const previousTodos = todos; // שמור backup
setTodos(prev => prev.map(t => // עדכן UI מיד
t.id === id ? { ...t, done: !t.done } : t
));
try {
await api.patch(`/todos/${id}`, { done: !todos.find(t=>t.id===id).done });
} catch {
setTodos(previousTodos); // Rollback!
alert('לא הצלחנו לעדכן. נסה שוב.');
}
}
✏תרגול — Axios + States4 שאלות
שאלה 1 — Axios vs Fetch
מה היתרון הגדול ביותר של Axios על Fetch?
✓ תשובה: ג) — לגאמא: "בחרתי Axios כי Token מנוהל פעם אחת ב-interceptor ולא בכל בקשה." + auto-error על 4xx/5xx = פחות קוד boilerplate.
שאלה 2 — Missing Error State
מה קורה אם fetch נכשל ואין Error State?
✓ תשובה: ג) Spinner אינסופי — loading=true לעולם לא ישתנה ל-false כי catch לא מטפל בsetLoading(false). תמיד: data + loading + error. תמיד!
שאלה 3 — Optimistic Update
מה Optimistic Update ולמה?
✓ תשובה: א) — Optimistic: עדכן מיד + Rollback. Pessimistic: מחכה לשרת. Optimistic = UX טוב לפעולות שכמעט תמיד מצליחות (toggle, like). Pessimistic = תשלום, מחיקה.
שאלה 4 — Interceptors
למה Interceptor ב-Axios עדיף על הוספת Token ידנית בכל בקשה?
✓ תשובה: ב) — DRY (Don't Repeat Yourself). Interceptor = Middleware על כל בקשה. אם Token משתנה — משנים במקום אחד.
// פרק 5
Custom Hook — useFetch
כל קומפוננטה שצריכה נתונים כותבת את אותו קוד. Custom Hook = מרכז הלוגיקה ומחזיר { data, loading, error }.
שיא השיעור: לממש את הלוגיקה שמאחורי ה-Web — פרסור, אירועים, state ואבטחה. פתרו את החובה ואז את האתגר; ההגשה נבדקת אוטומטית מול מקרי-קצה נסתרים.
T2 · בסיסי200 נק'~15 דק'algorithmic
ניווט בטוח בתגובת API (JSON path)
תגובות API הן JSON מקונן; לעיתים שדות חסרים. ממש get_path(data, path, default=None) שמנווט לפי נתיב מופרד-בנקודות (מפתחות במילונים, אינדקסים ברשימות — כולל שליליים). אם צעד כלשהו נכשל (מפתח חסר / אינדקס מחוץ לתחום / טיפוס לא-מתאים) — החזר את default במקום לקרוס.
def get_path(data, path, default=None):
cur = data
for part in path.split('.'):
if isinstance(cur, dict) and part in cur:
cur = cur[part]
elif isinstance(cur, list) and part.lstrip('-').isdigit():
i = int(part)
if -len(cur) <= i < len(cur): cur = cur[i]
else: return default
else:
return default
return cur
T3 · בינוני400 נק'~30 דק'algorithmic
Rate Limiter — דלי אסימונים (Token Bucket)
הגן על API מפני הצפה עם מגביל-קצב 'דלי אסימונים'. הדלי מתחיל מלא (capacity אסימונים) ומתמלא ב-refill אסימונים לשנייה (חסום ל-capacity). נתונה רשימת חותמות-זמן requests (לא יורדת, בשניות). עבור כל בקשה: מלא לפי הזמן שחלף, ואז אם יש ≥1 אסימון — צרוך אחד ואשר (True); אחרת דחה (False). החזר רשימת bool באורך הבקשות.
def rate_limit(requests, capacity, refill):
tokens = float(capacity); last = None; out = []
for t in requests:
if last is not None:
tokens = min(capacity, tokens + (t - last) * refill) # מילוי מאז הבקשה הקודמת
last = t
if tokens >= 1: tokens -= 1; out.append(True) # מותר
else: out.append(False) # נחסם
return out
שאלות תרגול ותשובות (10)
שאלה 1 · מה מחזירה הקריאה ל-fetch?
const res = fetch("https://api.example.com/users");
א. את גוף התשובה כטקסט
ב. אובייקט JSON מוכן
ג. Promise שנפתר לאובייקט Response
ד. מערך של תוצאות
תשובה: ג
fetch מחזירה Promise. גם אחרי await מקבלים Response — צריך .json() כדי לקבל את הנתונים.
שאלה 2 · למה צריך שתי פעמים await?
const res = await fetch(url);
const data = await res.json();
א. כי res.json() גם היא אסינכרונית וקוראת את ה-stream
ב. כי fetch איטית
ג. כדי לוודא שהשרת חי
ד. זו טעות — מספיק await אחד
תשובה: א
ה-headers מגיעים ראשונים; קריאת הגוף ופענוח ה-JSON הם שלב אסינכרוני נוסף.
שאלה 3 · מה יקרה כשהשרת מחזיר 404?
try {
const res = await fetch(url);
const data = await res.json();
} catch (err) {
console.log("error");
}
א. ה-catch יתפוס את השגיאה
ב. fetch לא זורקת על 404 — צריך לבדוק res.ok
ג. הקוד יקרוס
ד. התשובה תהיה null
תשובה: ב
fetch נכשלת רק על תקלת רשת. סטטוס שגיאה מחייב בדיקה: if (!res.ok) throw new Error(res.status).
בלי Content-Type השרת לא יודע לפרש את הגוף כ-JSON ולרוב יחזיר 400.
שאלה 5 · מה עיקר ההבדל בין axios ל-fetch?
א. axios עובד רק בדפדפן
ב. axios מפענח JSON אוטומטית וזורק שגיאה על סטטוס 4xx/5xx
ג. fetch מהירה משמעותית
ד. axios לא תומך ב-POST
תשובה: ב
axios מחזירה response.data מפוענח וזורקת על סטטוס שגיאה — פחות קוד תחזוקה.
שאלה 6 · השרת החזיר 400 Bad Request. מי אשם?
א. השרת קרס
ב. הלקוח — הבקשה שגויה או חסרה שדות
ג. חומת האש חסמה
ד. ה-DNS נכשל
תשובה: ב
🎯 דגש למבחן — 4xx מול 5xx: 4xx = הלקוח טעה (400 בקשה שגויה, 401 לא מאומת, 403 אין הרשאה, 404 לא נמצא). 5xx = השרת טעה (500 שגיאה פנימית, 502 שער פגום, 503 לא זמין). האבחנה הזו מראה שאתם מבינים את מודל Client-Server — ותמיד נשאלת בראיון.
שאלה 7 · מהי שגיאת CORS?
א. הדפדפן חוסם תשובה מדומיין אחר שלא אישר את מקור הבקשה
ב. סיסמת ה-API שגויה
ג. הבקשה חרגה מזמן ההמתנה
ד. ה-JSON פגום
תשובה: א
CORS נאכף בדפדפן. הפתרון הוא בצד השרת — כותרת Access-Control-Allow-Origin.