כללי ההוקים הם שניים: קוראים להוקים רק ברמה העליונה של הקומפוננטה, אף פעם לא בתוך תנאי, לולאה, פונקציה מקוננת או אחרי return מוקדם; וקוראים להם רק מקומפוננטות פונקציה או מהוקים מותאמים. הם קיימים כי React מבחינה בין הוקים לפי הסדר שבו קוראים להם, ולכן הסדר הזה חייב להיות זהה בכל רינדור.
React שומרת את הערכים של ההוקים של קומפוננטה ברשימה שמחוברת לאותה קומפוננטה. בכל רינדור היא עוברת על הרשימה: הקריאה הראשונה להוק מקבלת את המקום הראשון, הקריאה השנייה את המקום השני, וכן הלאה. אין שמות, רק מיקומים. הדוגמה שמתחת היא גרסת צעצוע של הרעיון הזה בכמה שורות של JavaScript רגיל, לא הקוד של React עצמה, כך שאפשר לשבור אותה בבטחה.
הרינדור הראשון ממלא שלושה מקומות: Ada, Countess, 36. לחצו על "Render without nickname": הקריאה המותנית מדולגת, ולכן הקריאה של age לוקחת את מקום 1 וקוראת Countess. React האמיתית שמה לב לאי ההתאמה בחלק מהמקרים וזורקת שגיאה, ובאחרים מעבירה את הערך הלא נכון להוק הלא נכון, מה שגרוע יותר. העבירו את הקריאה toyUseState('Countess') אל מעל ה-if ושני הכפתורים נותנים את הגיל הנכון.
שני הכללים בקוד
function Profile({ user }) {
// Good: always called, always in this order
const [tab, setTab] = useState('posts');
const theme = useContext(ThemeContext);
useEffect(() => {
document.title = user.name;
}, [user.name]);
// Bad: inside a condition, a loop or a nested function
if (user.isAdmin) {
const [log, setLog] = useState([]); // breaks the order
}
}
קריאה להוק שתמיד מגיעים אליה, באותו סדר, בסדר בכל מקום בגוף הקומפוננטה, כל עוד היא באה לפני כל return מוקדם.
תיקון הוק שנקרא בתוך תנאי
ה-linter מדווח על זה כ-React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. התיקון תמיד זהה: קוראים להוק בכל פעם, ושמים את התנאי בתוכו.
// Broken: the effect exists on some renders and not others
function Notifications({ user }) {
if (user) {
useEffect(() => {
console.log('subscribe for', user.name);
}, [user]);
}
return null;
}
האפקט מוצהר בכל רינדור, וה-if שבתוכו מחליט אם לעשות משהו. התחברו והתנתקו כמה פעמים וה-Console מציג subscribe לכל התחברות ו-unsubscribe לכל התנתקות.
הוקים אחרי return מוקדם
גם return מוקדם הוא תנאי. זה מייצר "Rendered more hooks than during the previous render" בפעם הראשונה ש-loading הופך ל-false, כי הרינדור הזה מגיע להוק שהקודם אף פעם לא קרא לו. כשרינדור קורא לפחות הוקים מהקודם, ההודעה היא "Rendered fewer hooks than expected".
// Broken: useState is skipped while loading
function Comments({ loading, comments }) {
if (loading) return <p>Loading...</p>;
const [expanded, setExpanded] = useState(false);
return <List items={comments} expanded={expanded} />;
}
כל הוק יושב מעל ה-return, כך ששני הענפים קוראים לאותם הוקים. לחצו על "Toggle loading", הרחיבו את הרשימה, ואז החליפו את מצב הטעינה פעמיים: הרשימה זוכרת שהיא הייתה מורחבת, כי ה-state אף פעם לא דולג.
הוקים בלולאה
אי אפשר לקרוא ל-useState פעם אחת לכל פריט בלולאה, כי מספר הפריטים משתנה. תנו לכל פריט קומפוננטה משלו וקראו להוק שם; לכל מופע יש מקומות משלו.
"Invalid hook call"
ההודעה מתחילה ב-"Invalid hook call. Hooks can only be called inside of the body of a function component." יש לה שלוש סיבות רגילות:
- ההוק רץ כש-React לא מרנדרת קומפוננטת פונקציה. ב-event handler, בתוך callback של אפקט, בקומפוננטת class, או ברמת המודול. העבירו אותו לגוף הקומפוננטה או להוק מותאם, וקראו להוק המותאם מהקומפוננטה.
- הגרסאות של
reactו-react-domלא תואמות. התקינו את אותה גרסה של שתיהן. - יש שני עותקים של React. חבילה מקומית מקושרת או ספרייה שאורזת React משלה מביאות עותק שני, והוקים מעותק אחד לא עובדים עם הרנדרר של השני. הריצו
npm ls reactוודאו שמופיעה רק גרסה אחת.
npm ls react
התצוגה המקדימה כאן רצה כמו build של פרודקשן, שלא מדפיס את ההודעות האלה: הוק שנקרא מ-event handler נכשל עם TypeError רגיל כמו "Cannot read properties of null", והשגיאות של React עצמה מגיעות כקודים קצרים של "Minified React error". הריצו את האפליקציה שלכם במצב פיתוח כדי לקרוא את הטקסט המלא.
תנו ל-linter לאכוף את זה
eslint-plugin-react-hooks בודק את שני הכללים תוך כדי הקלדה, וגם בודק מערכי תלויות של אפקטים. תבנית ה-React של Vite והגדרות ה-ESLint של Next.js כוללות אותו. בהגדרת ESLint שטוחה (flat config), הפעילו את ה-preset המומלץ שלו:
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
export default defineConfig([reactHooks.configs.flat.recommended]);
שני כללי הליבה הם react-hooks/rules-of-hooks (שגיאה) ו-react-hooks/exhaustive-deps (אזהרה). מאז גרסה 7 ה-preset מפעיל גם כללים שמגיעים מ-React Compiler, כמו react-hooks/purity ו-react-hooks/refs, שמסמנים רינדורים לא טהורים וקריאה של refs בזמן הרינדור. כדי לבחור כללים אחד אחד, רשמו את הפלאגין בתור plugins: { 'react-hooks': reactHooks } ופרטו את הכללים שאתם רוצים.
הפלאגין מוצא הוקים לפי שם: כל פונקציה ששמה מתחיל ב-use ואחריו אות גדולה נחשבת להוק, ורק קומפוננטות (שמות באות גדולה) ופונקציות use אחרות יכולות לקרוא לה. לכן הוקים מותאמים אישית חייבים להשתמש בקידומת.
החריג: use
use, שנוסף ב-React 19, קורא promise או קונטקסט, ובניגוד לכל הוק אחר אפשר לקרוא לו בתוך תנאי או לולאה. עדיין צריך לקרוא לו בזמן הרינדור, בתוך קומפוננטה או הוק.
רק הכותרת עם ערכת הנושא משנה צבע כשמחליפים את ערכת הנושא. נסו את אותו דבר עם useContext בתוך ה-if וה-linter מסמן את זה. הדף על ההוק use מכסה קריאת promises איתו.
שאלות נפוצות
מה הם כללי ההוקים?
שני כללים. קוראים להוקים רק ברמה העליונה של קומפוננטת פונקציה או הוק מותאם, אף פעם לא בתוך תנאים, לולאות, פונקציות מקוננות או אחרי return מוקדם. וקוראים להוקים רק מקומפוננטות פונקציה או מהוקים מותאמים, לא מפונקציות רגילות או מקומפוננטות class.
למה אי אפשר לקרוא להוקים בתוך תנאי?
React לא מכירה את ההוקים שלכם לפי שם. היא שומרת את הערכים שלהם ברשימה ומחזירה אותם לפי הסדר שבו הקריאות קורות. אם תנאי מדלג על קריאה, כל הוק מאוחר יותר קורא את הערך ששייך להוק שלפניו.
מה המשמעות של "Rendered more hooks than during the previous render"?
רינדור קרא ליותר הוקים מהרינדור שלפניו, בדרך כלל כי הוק יושב בתוך if או אחרי return מוקדם שרץ הפעם אחרת. העבירו כל הוק אל מעל התנאי ושימו את התנאי בתוך ההוק במקום.
איך מתקנים "Invalid hook call"?
בדקו שלושה דברים: ההוק רץ בזמן ש-React מרנדרת קומפוננטת פונקציה (לא ב-event handler, ב-callback של אפקט, בקומפוננטת class או ברמת המודול), ל-react ול-react-dom יש גרסאות תואמות, ולאפליקציה יש רק עותק אחד של React (npm ls react מציג אותם).
אפשר לקרוא להוק use בתוך תנאי?
כן. use (React 19) הוא החריג היחיד: אפשר לקרוא לו בתוך בלוקים של if ובלולאות. עדיין צריך לקרוא לו בתוך קומפוננטה או הוק, לא ב-event handler.