Rules of Hooks
Hooks rely on call order. React associates each hook call with a position in the component's render.
That is why hooks have strict rules.
Rule 1: Call Hooks at the Top Level
Do not call hooks inside conditions, loops, nested functions, or early-return branches.
function Profile({ user }) {
if (!user) {
return null;
}
const [isEditing, setIsEditing] = useState(false); // risky
}If user changes from missing to present, the hook call order changes.
Put hooks before conditional returns.
function Profile({ user }) {
const [isEditing, setIsEditing] = useState(false);
if (!user) {
return null;
}
}Rule 2: Call Hooks Only from React Functions
Hooks can be called from:
- React function components
- custom hooks
Hooks should not be called from regular utility functions, event handlers, class components, or module-level code.
function handleClick() {
const [count, setCount] = useState(0); // wrong
}Custom Hook Names
Custom hooks must start with use.
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
return isOnline;
}The name tells React tooling that the function follows hook rules.
Conditions Go Inside Hooks
If logic is conditional, keep the hook call in place and put the condition inside.
useEffect(() => {
if (!roomId) {
return;
}
const connection = connect(roomId);
return () => connection.disconnect();
}, [roomId]);Dependency Rule
Effects, memoized values, and callbacks should list reactive values they use.
useEffect(() => {
document.title = title;
}, [title]);The dependency lint rule is not just style. It catches stale closures where the hook reads an old value.
Common Mistakes
Do not silence hook lint warnings as a reflex. First ask whether the logic belongs in an event handler, render calculation, reducer, or effect.
Do not create hooks dynamically.
fields.forEach((field) => {
useState(field.initialValue); // wrong
});Use one state object or a reducer for dynamic data.
Why must hooks be called in the same order on every render?
Practice Challenge
Fix this component:
function Dashboard({ userId, enabled }) {
if (!enabled) {
return null;
}
const [items, setItems] = useState([]);
useEffect(() => {
loadItems(userId).then(setItems);
}, []);
return <ItemList items={items} />;
}Requirements:
- keep hook order stable
- include correct dependencies
- avoid loading when disabled is false
- handle stale async results if
userIdchanges quickly
Recap
Hooks must be called consistently from React components or custom hooks. Keep hook calls at the top level, put conditions inside hooks, and treat dependency warnings as clues about stale or misplaced logic.