React · Topic 1 of 8
Rules of hooks and custom hooks
What you will learn
Hooks are functions that let components use React features like state and effects. They only work if React can match them to the same slots on every render, which is why the Rules of Hooks exist. Custom Hooks let you reuse stateful logic without sharing the state itself.
Why the rules exist
React does not remember Hooks by name. It remembers them by the order they are called during each render. If the order changes, state ends up attached to the wrong Hook.
The rules protect that order.
Example
Why the rules exist
function Profile() {
const [name, setName] = useState(''); // slot 1
const [age, setAge] = useState(0); // slot 2
useEffect(() => { /* ... */ }); // slot 3
}Rule 1: top level only
Call Hooks at the top level of your component. Never inside conditions, loops, nested functions, try/catch, or event handlers. Also never after an early return.
If you need a condition, put it inside the Hook, or move the Hook into a child component.
Example
Rule 1: top level only
// Wrong
if (!user) return null;
const [tab, setTab] = useState(0);
// Right
const [tab, setTab] = useState(0);
if (!user) return null;Rule 2: only from React functions
Call Hooks from function components or from custom Hooks. Do not call them from regular JavaScript functions, class components, or event handlers.
In React 19 the use API is the exception to the conditional rule: it can be called inside if statements and loops, but not inside try/catch.
Writing a custom Hook
A custom Hook is a function whose name starts with use and that calls other Hooks. Extract one when several components repeat the same stateful logic.
The name matters: the linter only enforces the rules on functions it recognizes as Hooks.
Example
Writing a custom Hook
function useOnline() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const on = () => setOnline(true);
const off = () => setOnline(false);
window.addEventListener('online', on);
window.addEventListener('offline', off);
return () => {
window.removeEventListener('online', on);
window.removeEventListener('offline', off);
};
}, []);
return online;
}Logic is shared, state is not
Each call to a custom Hook gets its own state. Two components using useOnline each have a separate online value.
To truly share state, lift it up to a common parent, use context, or use an external store with useSyncExternalStore.
Keep Hooks stable and linted
Turn on eslint-plugin-react-hooks, including exhaustive-deps. It catches order violations and stale dependencies.
Return stable values from custom Hooks. A new object or inline function on every render makes consumers' effects re-run. The React Compiler also assumes you follow these rules.
Key points
- React tracks Hooks by call order, so the order must never change between renders.
- Call Hooks at the top level, before any early return, never in loops, conditions, or handlers.
- Only call Hooks from function components or custom Hooks.
- Custom Hooks must start with use and can call other Hooks.
- Custom Hooks share logic, not state: each call has its own state.
- Use eslint-plugin-react-hooks to catch mistakes automatically.
Common mistakes
- Calling a Hook after an early return or inside an if statement.
- Naming a function that uses Hooks without the use prefix, so the linter skips it.
- Assuming two components using one custom Hook share the same state.
- Forgetting effect cleanup in a custom Hook that subscribes to events.
- Returning a new object or inline callback each render and putting it in an effect's dependency array.
Lesson complete
Ready to check what you learned?
Swipe left or right, or use the buttons.