Hook Rules
Rules of Hooks explain why Hooks must be called at the top level of React functions, how call order works, and common conditional or nested call mistakes.
React Hooks provide a powerful way to use state, Effects, refs, and other React features inside function components.
With that power comes an important requirement:
Hooks must be called according to a consistent set of rules.
These rules allow React to correctly preserve Hook state and behavior between renders.
The two fundamental Rules of Hooks are:
- Only call Hooks at the top level.
- Only call Hooks from React function components or Custom Hooks.
Understanding these rules is essential before building larger React applications.
Why Do Rules of Hooks Exist?
Consider:
const [count, setCount] = useState(0);const [name, setName] = useState("");
React needs to associate each Hook call with the correct piece of state.
Conceptually:
First useState → countSecond useState → name
React relies on the order of Hook calls remaining consistent between renders.
If Hook calls are conditional, their order can change.
For example:
if (isLoggedIn) {useState("user");}
On one render, the Hook may be called.
On another render, it may not.
That makes the Hook ordering inconsistent.
The Rules of Hooks prevent this problem.
Rule 1: Only Call Hooks at the Top Level
Hooks should be called at the top level of a React component or Custom Hook.
Do not call Hooks inside:
- conditions
- loops
- nested functions
- event handlers
try/catch/finallyblocks- other callbacks
Do Not Call Hooks Inside Conditions
Incorrect:
function Profile({ isLoggedIn }) {if (isLoggedIn) {const [user, setUser] = useState(null);}return <div>Profile</div>;}
The Hook is conditional.
Instead:
function Profile({ isLoggedIn }) {const [user, setUser] = useState(null);if (!isLoggedIn) {return <p>Please log in.</p>;}return <div>Profile</div>;}
The Hook is always called.
The condition controls what the component renders, not whether the Hook exists.
Do Not Call Hooks Inside Loops
Incorrect:
function Products({ products }) {for (const product of products) {const [selected, setSelected] = useState(false);}return <div>Products</div>;}
The number of Hook calls could change when the number of products changes.
Instead, move the Hook into a child component:
function Product({ product }) {const [selected, setSelected] = useState(false);return (<div><button onClick={() => setSelected((value) => !value)}>{selected ? "Selected" : "Select"}</button><span>{product.name}</span></div>);}function Products({ products }) {return (<div>{products.map((product) => (<Productkey={product.id}product={product}/>))}</div>);}
Each Product component has its own state.
This is a common and important React pattern.
Do Not Call Hooks Inside Nested Functions
Incorrect:
function Component() {function initialize() {const [value, setValue] = useState(0);}return <div />;}
The Hook is inside a nested function.
Instead:
function Component() {const [value, setValue] = useState(0);function initialize() {setValue(0);}return <div />;}
Hooks belong at the top level.
Do Not Call Hooks Inside Event Handlers
Incorrect:
function Component() {function handleClick() {const [value, setValue] = useState(0);}return (<button onClick={handleClick}>Click</button>);}
An event handler runs in response to an event. It is not a component render.
Instead:
function Component() {const [value, setValue] = useState(0);function handleClick() {setValue((current) => current + 1);}return (<button onClick={handleClick}>{value}</button>);}
Do Not Call Hooks Inside Callbacks
Incorrect:
function Component() {const calculate = () => {const [value, setValue] = useState(0);};return <div />;}
Hooks must not be placed inside arbitrary callbacks.
This also applies to callbacks passed to array methods:
items.map(() => {const [value, setValue] = useState(0);});
If each item needs state, create a component for the item.
Do Not Call Hooks Inside try/catch/finally
Avoid:
function Component() {try {const [value, setValue] = useState(0);} catch (error) {// ...}return <div />;}
Hook calls should remain at the top level.
Instead:
function Component() {const [value, setValue] = useState(0);try {// regular logic} catch (error) {// handle error}return <div />;}
The Hook itself remains unconditional.
The Correct Pattern
This is good:
function Dashboard() {const [user, setUser] = useState(null);const [loading, setLoading] = useState(false);const [error, setError] = useState(null);if (loading) {return <p>Loading...</p>;}if (error) {return <p>Error</p>;}return <p>Dashboard</p>;}
Notice:
Hooks↓Conditional rendering↓UI
Not:
Conditional logic↓Hooks
Rule 2: Only Call Hooks from React Functions
Hooks should be called from:
- React function components
- Custom Hooks
Good:
function Counter() {const [count, setCount] = useState(0);return <p>{count}</p>;}
Good:
function useCounter() {const [count, setCount] = useState(0);return [count, setCount];}
Avoid calling Hooks from ordinary utility functions.
Hooks Inside Regular Functions
Incorrect:
function calculateValue() {const [value, setValue] = useState(0);return value;}
If calculateValue is just a utility function, it should not call Hooks.
Instead:
function calculateValue(value) {return value * 2;}
If the function genuinely represents reusable stateful behavior, turn it into a Custom Hook:
function useValue() {const [value, setValue] = useState(0);return [value, setValue];}
The name should communicate that it is a Hook.
Custom Hooks Must Follow the Same Rules
A Custom Hook can call other Hooks:
function useOnlineStatus() {const [isOnline, setIsOnline] = useState(true);useEffect(() => {// ...}, []);return isOnline;}
But the Custom Hook itself must follow the Rules of Hooks.
Incorrect:
function useOnlineStatus(enabled) {if (enabled) {useEffect(() => {// ...}, []);}return true;}
Instead:
function useOnlineStatus(enabled) {useEffect(() => {if (!enabled) {return;}// ...}, [enabled]);return true;}
The Hook is always called.
The condition is inside the Hook's logic.
Conditional Logic Inside Hooks
A common misunderstanding is:
If I cannot conditionally call a Hook, how do I make a Hook conditional?
The answer is to keep the Hook call unconditional and put the condition inside the Hook.
Incorrect:
if (enabled) {useEffect(() => {// ...}, []);}
Correct:
useEffect(() => {if (!enabled) {return;}// Effect logic}, [enabled]);
This preserves the Hook call order.
Conditional Effects
Suppose a subscription should only be active when enabled.
Use:
useEffect(() => {if (!enabled) {return;}const connection = connect();return () => {connection.disconnect();};}, [enabled]);
The Effect Hook is always called.
The Effect decides whether there is work to perform.
This pattern is useful for:
- subscriptions
- timers
- event listeners
- API synchronization
- browser APIs
Conditional State Initialization
You may need state that depends on a condition.
Do not conditionally call useState.
Incorrect:
if (isAdmin) {const [settings, setSettings] = useState({});}
Instead:
const [settings, setSettings] = useState(isAdmin ? defaultAdminSettings : defaultUserSettings);
Or use a lazy initializer:
const [settings, setSettings] = useState(() =>createInitialSettings(isAdmin));
The Hook call itself remains unconditional.
Hooks and Early Returns
This is an important distinction.
An early return after all Hook calls is fine:
function Profile({ user }) {const [loading, setLoading] = useState(false);if (!user) {return <p>No user.</p>;}return <p>{user.name}</p>;}
But this is problematic:
function Profile({ user }) {if (!user) {return <p>No user.</p>;}const [loading, setLoading] = useState(false);return <p>{user.name}</p>;}
On some renders, the Hook is called and on others it is not.
The safe pattern is:
Call Hooks↓Check conditions↓Return UI
Hooks and Component Loops
A common beginner mistake is trying to create state dynamically inside a loop.
Incorrect:
function Form({ fields }) {return fields.map((field) => {const [value, setValue] = useState("");return (<inputkey={field.name}value={value}onChange={(event) => setValue(event.target.value)}/>);});}
Instead, extract a component:
function Field({ field }) {const [value, setValue] = useState("");return (<inputvalue={value}onChange={(event) => setValue(event.target.value)}/>);}function Form({ fields }) {return (<>{fields.map((field) => (<Fieldkey={field.name}field={field}/>))}</>);}
Now each field is a separate component instance with its own state.
Hooks and Dynamic Component Data
React's component model naturally solves many dynamic-state problems.
Instead of:
One component├── Hook for item 1├── Hook for item 2├── Hook for item 3└── ...
Use:
Parent Component↓List of Child Components├── Item 1 → own Hooks├── Item 2 → own Hooks└── Item 3 → own Hooks
This makes state ownership explicit.
Hook Order
The Rules of Hooks are fundamentally about preserving a stable order of Hook calls.
Consider:
function Component() {const [a, setA] = useState("A");const [b, setB] = useState("B");const [c, setC] = useState("C");// ...}
Conceptually:
Hook #1 → aHook #2 → bHook #3 → c
React expects that order to remain consistent.
Do not change it with conditional Hook calls.
What Goes Wrong with Conditional Hooks?
Imagine:
function Component({ showExtra }) {const [first, setFirst] = useState("first");if (showExtra) {const [second, setSecond] = useState("second");}const [third, setThird] = useState("third");// ...}
When showExtra is true:
Hook #1 → firstHook #2 → secondHook #3 → third
When it is false:
Hook #1 → firstHook #2 → third
The positions no longer represent the same state variables.
This is why conditional Hook calls are invalid.
Hook Order Mental Model
Think of Hooks as a sequence:
Render 1useStateuseEffectuseRefuseMemo
Render 2 should have the same sequence:
Render 2useStateuseEffectuseRefuseMemo
Not:
Render 2useStateuseRefuseMemo
The order must remain stable.
Hooks and Nested Components
Hooks belong to the component whose state they manage.
Good:
function UserCard({ user }) {const [expanded, setExpanded] = useState(false);return (<article><buttononClick={() =>setExpanded((value) => !value)}>Toggle</button>{expanded && <p>{user.email}</p>}</article>);}
If you have multiple user cards:
function UserList({ users }) {return (<>{users.map((user) => (<UserCardkey={user.id}user={user}/>))}</>);}
Each UserCard gets its own Hook state.
Hooks and Component Definitions
Avoid defining components dynamically inside another component when it causes unnecessary remounting or makes Hook ownership confusing.
Prefer:
function UserCard({ user }) {const [expanded, setExpanded] = useState(false);return <div>{/* ... */}</div>;}function UserList({ users }) {return users.map((user) => (<UserCardkey={user.id}user={user}/>));}
This gives React a stable component type.
The eslint-plugin-react-hooks Rule
The React ecosystem provides ESLint support for detecting many incorrect Hook patterns.
A typical React project can use the Hooks lint rules to catch issues such as:
- Hooks called conditionally
- Hooks called inside loops
- invalid dependency arrays
- incorrect Custom Hook usage
The lint rules are extremely useful because many Hook mistakes are structural and can be detected before runtime.
A modern React project should generally keep its React Hooks linting enabled.
Rules of Hooks vs Dependency Rules
These concepts are related but different.
Rules of Hooks
They answer:
Where can Hooks be called?
Examples:
useState()useEffect()useRef()
They must be called from valid locations and at the top level.
Effect Dependency Rules
They answer:
Which reactive values does an Effect, Memo, or Callback depend on?
Example:
useEffect(() => {console.log(user.name);}, [user]);
These are different concerns.
A component can follow the Rules of Hooks while still having an incorrect dependency array.
Both need attention.
Example: Correct Hook Usage
import {useEffect,useMemo,useState,} from "react";function ProductList({ products, query }) {const [isVisible, setIsVisible] = useState(true);const filteredProducts = useMemo(() => {return products.filter((product) =>product.name.toLowerCase().includes(query.toLowerCase()));}, [products, query]);useEffect(() => {document.title = `${filteredProducts.length} Products`;}, [filteredProducts.length]);if (!isVisible) {return null;}return (<ul>{filteredProducts.map((product) => (<li key={product.id}>{product.name}</li>))}</ul>);}
All Hooks are called before the conditional return.
Example: Incorrect Hook Usage
function ProductList({ products, query, enabled }) {if (!enabled) {return null;}const [isVisible, setIsVisible] = useState(true);if (query) {useEffect(() => {// ...}, [query]);}return <div />;}
Problems:
useStateoccurs after an early return.useEffectis conditional.
A safer structure:
function ProductList({ products, query, enabled }) {const [isVisible, setIsVisible] = useState(true);useEffect(() => {if (!enabled || !query) {return;}// Effect logic}, [enabled, query]);if (!enabled) {return null;}return <div />;}
Hooks in Custom Hook Composition
Custom Hooks can call other Custom Hooks at the top level.
Good:
function useProductSearch(query) {const debouncedQuery = useDebounce(query, 500);const { data, loading, error } =useProducts(debouncedQuery);return {data,loading,error,};}
The calls remain unconditional.
Each Custom Hook can manage its own internal logic while following the same rules.
Do Not Hide Conditional Hook Calls
Renaming a function does not make invalid Hook usage valid.
For example:
function createState() {return useState(0);}
Calling:
createState();
does not make the Hook safe.
If a function uses Hooks, it should be clearly designed as a Custom Hook:
function useCounter() {return useState(0);}
Then use it from a component or another Custom Hook:
function Counter() {const [count, setCount] = useCounter();return <p>{count}</p>;}
Common Mistakes
1. Calling Hooks in if Statements
Incorrect:
if (condition) {useEffect(() => {});}
Call the Hook unconditionally.
2. Calling Hooks in Loops
Incorrect:
items.forEach(() => {useState();});
Move the state into a child component or use a single state structure.
3. Calling Hooks in Event Handlers
Incorrect:
function handleClick() {useState();}
Use state at the component level.
4. Calling Hooks in Utility Functions
Incorrect:
function formatData() {useState();}
Use a normal helper for pure calculations.
5. Calling Hooks After an Early Return
Incorrect:
if (!user) {return null;}const [value, setValue] = useState("");
Move the Hook before the return.
6. Creating Dynamic Hook Counts
Avoid:
items.map(() => useState());
The number of Hook calls should not depend on dynamic data.
7. Confusing Conditional Logic with Conditional Hook Calls
This is allowed:
useEffect(() => {if (!enabled) {return;}// logic}, [enabled]);
This is not:
if (enabled) {useEffect(() => {// logic}, []);}
Best Practices
1. Keep Hooks at the Top
A clean component often starts with:
function Component() {const state = useState();const ref = useRef();useEffect(() => {// ...}, []);// rendering logic}
2. Separate Hook Calls from Rendering Logic
A useful mental structure is:
Component├── Hook calls├── Event handlers├── Derived values└── JSX
3. Use Child Components for Repeated Stateful Items
If each item needs independent state, create a component for the item.
4. Keep Custom Hooks Clearly Named
Use:
useSearch()useAuth()useForm()
This makes Hook usage obvious.
5. Keep Hook Dependencies Accurate
Rules of Hooks do not eliminate the need to maintain correct dependency arrays.
6. Enable Hook Linting
Use React's ESLint Hook rules to catch structural problems early.
7. Avoid Clever Hook Abstractions
Do not hide Hook calls inside confusing utility layers.
Prefer straightforward, readable Hook composition.
Debugging Hook Rule Errors
When you see a Hook-related error, ask:
Question 1
Is the Hook called from a React component or Custom Hook?
Question 2
Is the Hook inside:
ifloopnested functionevent handlercallbacktry/catch
?
Question 3
Can an early return happen before the Hook?
Question 4
Can the number of Hook calls change between renders?
Question 5
Is the function actually a Custom Hook?
This checklist resolves many Hook-order problems.
Practical Example: Fixing a Conditional Hook
Incorrect:
function Chat({ connected }) {if (!connected) {return <p>Disconnected</p>;}useEffect(() => {console.log("Connected");}, []);return <p>Connected</p>;}
The Effect is not called when disconnected.
Correct:
function Chat({ connected }) {useEffect(() => {if (!connected) {return;}console.log("Connected");}, [connected]);if (!connected) {return <p>Disconnected</p>;}return <p>Connected</p>;}
Now the Hook is called on every render.
The Effect decides whether it has work to perform.
Practical Example: Per-Item State
Incorrect:
function TodoList({ todos }) {return todos.map((todo) => {const [editing, setEditing] = useState(false);return (<div key={todo.id}>{todo.text}</div>);});}
Correct:
function TodoItem({ todo }) {const [editing, setEditing] = useState(false);return (<div><buttononClick={() =>setEditing((value) => !value)}>Edit</button>{editing ? (<input defaultValue={todo.text} />) : (<span>{todo.text}</span>)}</div>);}function TodoList({ todos }) {return (<div>{todos.map((todo) => (<TodoItemkey={todo.id}todo={todo}/>))}</div>);}
Each TodoItem owns its own editing state.
Practice Exercises
Exercise 1: Fix Conditional Hooks
Given:
function Component({ enabled }) {if (enabled) {const [value, setValue] = useState(0);}return <div />;}
Rewrite it so it follows the Rules of Hooks.
Exercise 2: Fix a Conditional Effect
Given:
function Chat({ connected }) {if (connected) {useEffect(() => {connect();}, []);}return <div />;}
Rewrite the component correctly.
Exercise 3: Fix Hook Usage in a Loop
Given:
function List({ items }) {return items.map((item) => {const [selected, setSelected] = useState(false);return <div>{item.name}</div>;});}
Extract an appropriate child component.
Exercise 4: Identify the Invalid Hook
Which Hook usage is invalid?
function Example() {const [count, setCount] = useState(0);useEffect(() => {console.log(count);}, [count]);return <p>{count}</p>;}
function Example() {const [count, setCount] = useState(0);if (count > 0) {useEffect(() => {console.log(count);}, [count]);}return <p>{count}</p>;}
Explain why.
Exercise 5: Custom Hook Rules
Create:
useOnlineStatus()
Make sure:
- Hooks are called at the top level.
- Event listeners are registered in an Effect.
- cleanup is returned from the Effect.
- the Hook can be called from multiple components.
Exercise 6: Refactor a Dynamic Form
Create a form where each field is rendered through a child component.
Each field should manage its own temporary UI state without calling Hooks inside the parent's .map() callback.
Mini Project: Hook Rules Refactoring
Start with a deliberately problematic component:
function Dashboard({ user, items, enabled }) {if (!enabled) {return null;}if (user) {const [expanded, setExpanded] =useState(false);}return items.map((item) => {const [selected, setSelected] =useState(false);return (<div key={item.id}>{item.name}</div>);});}
Refactor it.
Requirements:
- no conditional Hook calls
- no Hook calls inside
.map() - use child components for item-level state
- keep Hooks at the top level
- preserve the UI behavior
- use clear component boundaries
Possible architecture:
Dashboard↓Dashboard-level Hooks↓ItemList↓Item↓Item-level Hooks
This exercise demonstrates why the Rules of Hooks influence component architecture.
Interview questions & answers
Interview preparation
8 questions grouped by difficulty — expand any card for a model answer and follow-up probe.
Beginner
1What are the Rules of Hooks?
BeginnerModel answer
The fundamental rules are:- Only call Hooks at the top level.
- Only call Hooks from React function components or Custom Hooks.
2Can Hooks be called inside event handlers?
BeginnerModel answer
No.
Event handlers run after rendering.
Hooks must be called during the component or Custom Hook's execution at the top level.
3Can you return early from a component before calling Hooks?
BeginnerModel answer
If that return can happen on some renders and Hooks would be called afterward on other renders, no. Call the Hooks first:const [state, setState] = useState(); → if (!data) { → return null; → }
Intermediate
4Why can't Hooks be called conditionally?
IntermediateModel answer
React relies on a stable order of Hook calls between renders.
Conditional calls can change that order and cause state to be associated with the wrong Hook.
5Can a Custom Hook call another Custom Hook?
IntermediateModel answer
Yes.function useSearch(query) { → const debouncedQuery = useDebounce(query, 500); → // ... → }This is normal Hook composition.6Why are Custom Hook names prefixed with use?
IntermediateModel answer
The convention identifies functions that use Hooks and allows React tooling and developers to recognize that they must follow the Rules of Hooks.
Advanced
7Can Hooks be used inside loops?
AdvancedModel answer
No.
If repeated items need independent Hook state, extract the repeated item into a component.
8Can you conditionally run an Effect?
AdvancedModel answer
Yes, but not by conditionally callinguseEffect. Correct:useEffect(() => { → if (!enabled) { → return; → } → // effect logic → }, [enabled]);
Rules of Hooks Cheat Sheet
Rule 1
Call Hooks at the top level.
Good:
function Component() {const [count, setCount] = useState(0);if (count > 10) {return <p>High</p>;}return <p>{count}</p>;}
Bad:
function Component() {if (condition) {const [count, setCount] = useState(0);}}
Rule 2
Call Hooks only from React functions.
Good:
function Component() {useState();}
Good:
function useSomething() {useState();}
Bad:
function helper() {useState();}
Conditional Effect
Good:
useEffect(() => {if (!enabled) {return;}// work}, [enabled]);
Bad:
if (enabled) {useEffect(() => {// work}, []);}
Repeated item state
Good:
function Item() {const [selected, setSelected] = useState(false);return <div />;}
Bad:
items.map(() => {const [selected, setSelected] = useState(false);});