Common Hook Mistakes in React JS
React Hooks are special functions that allow functional components to use state, effects, context, references, and other React features. Common Hook mistakes occur when developers call Hooks incorrectly, manage state improperly, use incorrect dependencies, or create unnecessary effects. Understanding these mistakes helps developers write clean, reliable, reusable, and maintainable React applications.
Course: JustAcademy React JS Training Course
Course Demo: Register for React JS Course Demo
1. What Are React Hooks?
React Hooks are functions that let functional components use React features such as state, effects, context, and references without writing class components.
Commonly used React Hooks include:
useState – manages component state.
useEffect – synchronizes a component with external systems.
useContext – reads values from React context.
useRef – stores a reference or mutable value that does not trigger a re-render when changed.
useMemo – caches a calculation result between renders.
useCallback – caches a function definition between renders.
useReducer – manages state using a reducer function.
Each Hook has its own purpose and rules. Using Hooks correctly helps prevent unexpected behavior and makes components easier to maintain.
2. Calling Hooks Inside Conditions
Mistake: Calling a Hook inside an if statement, conditional expression, or another conditional block.
React relies on Hooks being called in the same order during each render. Calling a Hook conditionally can change that order and cause errors.
Incorrect Example
import { useState } from "react";
function UserProfile({ isLoggedIn }) {
if (isLoggedIn) {
const [name, setName] = useState("Amit");
}
return <h2>User Profile</h2>;
}
Problem: The Hook is called only when isLoggedIn is true. When the condition changes, the order of Hook calls may become inconsistent.
Correct Example
import { useState } from "react";
function UserProfile({ isLoggedIn }) {
const [name, setName] = useState("Amit");
if (!isLoggedIn) {
return <p>Please log in.</p>;
}
return <h2>Welcome, {name}!</h2>;
}
Best practice: Call Hooks at the top level of a function component or custom Hook, before conditional returns.
3. Calling Hooks Inside Loops
Mistake: Calling Hooks inside for, while, or other loops.
Incorrect Example
function ProductList({ products }) {
for (const product of products) {
const [selected, setSelected] = useState(false);
}
return <h2>Product List</h2>;
}
Problem: The number of Hook calls may change when the number of products changes.
Correct Example
import { useState } from "react";
function ProductItem({ product }) {
const [selected, setSelected] = useState(false);
return (
<button onClick={() => setSelected(!selected)}>
{product.name}: {selected ? "Selected" : "Select"}
</button>
);
}
function ProductList({ products }) {
return (
<div>
{products.map(product => (
<ProductItem key={product.id} product={product} />
))}
</div>
);
}
Explanation: Each product has its own component and state. The Hook is called at the top level of ProductItem, not inside the loop.
4. Calling Hooks Inside Event Handlers
Mistake: Calling a Hook inside a button click handler or another event handler.
Incorrect Example
function Counter() {
function handleClick() {
const [count, setCount] = useState(0);
setCount(count + 1);
}
return <button onClick={handleClick}>Increase</button>;
}
Problem: Event handlers run in response to user actions, not as part of rendering a React component. Hooks cannot be called inside them.
Correct Example
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(currentCount => currentCount + 1);
}
return (
<button onClick={handleClick}>
Count: {count}
</button>
);
}
Best practice: Declare Hooks at the top level and use event handlers to update state or perform actions.
5. Updating State Directly
Mistake: Modifying a state variable directly instead of using its setter function.
Incorrect Example
function Counter() {
const [count, setCount] = useState(0);
function increaseCount() {
count = count + 1;
}
return <button onClick={increaseCount}>{count}</button>;
}
Problem: State is a snapshot for a particular render. Directly changing the variable does not tell React to update the component and is not a valid way to manage state.
Correct Example
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function increaseCount() {
setCount(count + 1);
}
return (
<button onClick={increaseCount}>
Count: {count}
</button>
);
}
Best practice: Use the setter function returned by useState to update state.
6. Ignoring the Previous State When Updating
Mistake: Using a state value directly when the next value depends on the previous value, especially when several updates are queued together.
Potentially Incorrect Example
function Counter() {
const [count, setCount] = useState(0);
function increaseThreeTimes() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
return (
<button onClick={increaseThreeTimes}>
Count: {count}
</button>
);
}
Problem: Each update uses the same value captured by the current render. The result may increase by only one instead of three.
Correct Example
function increaseThreeTimes() {
setCount(currentCount => currentCount + 1);
setCount(currentCount => currentCount + 1);
setCount(currentCount => currentCount + 1);
}
Explanation: Functional state updates receive the latest queued state value, so each update builds on the previous update.
7. Forgetting Dependencies in useEffect
Mistake: Leaving out reactive values used inside a useEffect callback.
Incorrect Example
import { useEffect, useState } from "react";
function UserGreeting() {
const [name, setName] = useState("Amit");
useEffect(() => {
console.log("Hello", name);
}, []);
return (
<button onClick={() => setName("Rahul")}>
Change Name
</button>
);
}
Problem: The effect does not rerun when name changes, because the dependency array is empty.
Correct Example
useEffect(() => {
console.log("Hello", name);
}, [name]);
Best practice: Include all reactive values used by an effect. Dependencies should match the code rather than being chosen simply to control how often the effect runs.
8. Creating Infinite Loops with useEffect
Mistake: Updating state inside an effect in a way that continually changes one of its dependencies.
Incorrect Example
import { useEffect, useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, [count]);
return <p>Count: {count}</p>;
}
Problem: The effect changes count. The state update causes another render, which changes the dependency and triggers the effect again.
Correct Approach
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(value => value + 1)}>
Count: {count}
</button>
);
}
Best practice: Use event handlers for actions caused by user interaction. Before adding state updates to an effect, check whether the effect is necessary and whether it is synchronizing with an external system.
9. Forgetting Cleanup in useEffect
Mistake: Starting a timer, subscription, or event listener without cleaning it up when it is no longer needed.
Incorrect Example
useEffect(() => {
const timerId = setInterval(() => {
console.log("Timer running");
}, 1000);
}, []);
Problem: The timer continues running after the component is removed unless it is cleared.
Correct Example
useEffect(() => {
const timerId = setInterval(() => {
console.log("Timer running");
}, 1000);
return () => {
clearInterval(timerId);
};
}, []);
Explanation: The cleanup function stops the timer when the effect is cleaned up, including when the component unmounts. React also runs cleanup before setting up the effect again when its dependencies change.
10. Using useEffect for Every Calculation
Mistake: Using an effect and extra state to calculate a value that can be derived directly from existing props or state.
Unnecessary Example
function ProductTotal({ price, quantity }) {
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(price * quantity);
}, [price, quantity]);
return <p>Total: ₹{total}</p>;
}
Problem: The total is calculated from values that are already available. Storing it separately can introduce an unnecessary render and duplicate state.
Better Example
function ProductTotal({ price, quantity }) {
const total = price * quantity;
return <p>Total: ₹{total}</p>;
}
Best practice: Calculate derived values during rendering. Use useMemo only when caching an expensive calculation is useful.
11. Using useMemo and useCallback Unnecessarily
Mistake: Adding useMemo or useCallback to every value or function without a clear performance reason.
Example
const fullName = useMemo(() => {
return firstName + " " + lastName;
}, [firstName, lastName]);
For a simple string concatenation, ordinary calculation is usually sufficient.
Simpler Approach
const fullName = firstName + " " + lastName;
Best practice: Start with simple code. Use memoization when profiling or component behavior shows that caching a calculation or function reference provides a real benefit.
12. Mutating Objects or Arrays in State
Mistake: Changing an existing object or array directly and then passing the same reference to the state setter.
Incorrect Example
function TodoList() {
const [todos, setTodos] = useState(["Learn React"]);
function addTodo() {
todos.push("Learn Hooks");
setTodos(todos);
}
return (
<button onClick={addTodo}>
Add Todo
</button>
);
}
Problem: The original array is mutated, and the same reference is passed back. React may not detect a changed state value as expected.
Correct Example
function addTodo() {
setTodos(currentTodos => [
...currentTodos,
"Learn Hooks"
]);
}
Best practice: Treat state objects and arrays as immutable. Create a new object or array when updating them.
13. Calling Hooks Outside React Components
Mistake: Calling Hooks inside ordinary JavaScript functions that are neither React function components nor custom Hooks.
Incorrect Example
function getUserName() {
const [name, setName] = useState("Amit");
return name;
}
Problem: This is a regular JavaScript function, so React cannot treat the Hook call as part of a component's rendering.
Correct Example: Custom Hook
import { useState } from "react";
function useUserName() {
const [name, setName] = useState("Amit");
return { name, setName };
}
function UserProfile() {
const { name, setName } = useUserName();
return (
<div>
<p>Name: {name}</p>
<button onClick={() => setName("Rahul")}>
Change Name
</button>
</div>
);
}
Best practice: Call Hooks only from React function components or custom Hooks. Custom Hook names should begin with use.
14. Using Hooks After an Early Return
Mistake: Returning from a component before all its regular Hooks have been called.
Incorrect Example
function Profile({ user }) {
if (!user) {
return <p>No user found</p>;
}
const [active, setActive] = useState(true);
return <p>{active ? user.name : "Inactive"}</p>;
}
Problem: When user is missing, the component returns before calling useState. This can change the order of Hook calls between renders.
Correct Example
function Profile({ user }) {
const [active, setActive] = useState(true);
if (!user) {
return <p>No user found</p>;
}
return <p>{active ? user.name : "Inactive"}</p>;
}
Best practice: Call all regular Hooks before conditional returns.
15. Ignoring React Strict Mode Behavior
Mistake: Assuming an effect will always run only once during development when the dependency array is empty.
When React Strict Mode is enabled, React may run an additional effect setup-and-cleanup cycle in development to help identify missing cleanup logic. This is intentional behavior and does not mean that React will perform the same extra cycle in production.
Example
useEffect(() => {
console.log("Effect started");
return () => {
console.log("Effect cleaned up");
};
}, []);
Best practice: Write effects with correct setup and cleanup behavior. Do not disable Strict Mode just to hide an effect-related bug.
16. Incorrectly Using useRef
Mistake: Expecting a change to ref.current to trigger a component re-render.
Example
const countRef = useRef(0);
function increaseCount() {
countRef.current += 1;
}
Changing countRef.current does not automatically update the UI. A ref is suitable for values that need to persist between renders without triggering a re-render.
Correct Approach for Displayed State
const [count, setCount] = useState(0);
function increaseCount() {
setCount(currentCount => currentCount + 1);
}
Best practice: Use useState for values that affect displayed UI and useRef for references or mutable values that do not need to trigger rendering.
17. Forgetting Keys When Rendering Lists
Mistake: Rendering lists without a stable, unique key for each item.
Incorrect Example
{products.map(product => (
<li>{product.name}</li>
))}
Correct Example
{products.map(product => (
<li key={product.id}>
{product.name}
</li>
))}
Explanation: Keys help React identify list items when they are added, removed, or reordered. Missing or unstable keys can cause warnings and incorrect component-state associations. Use a stable unique ID whenever possible.
18. Common Hook Mistakes Summary
| Common Mistake |
Why It Is a Problem |
Solution |
| Calling Hooks conditionally |
Changes Hook call order. |
Call Hooks at the top level. |
| Calling Hooks inside loops |
Hook call count may change. |
Move state into separate components or custom Hooks. |
| Calling Hooks in event handlers |
Event handlers are not component rendering. |
Declare Hooks at the component's top level. |
| Direct state mutation |
React may not detect updates correctly. |
Use setters and immutable updates. |
| Missing dependencies |
Effects can use outdated values. |
Include reactive dependencies. |
| Infinite effect loops |
Repeated state changes trigger repeated effects. |
Review effect purpose and state updates. |
| Missing cleanup |
Timers or subscriptions may continue unnecessarily. |
Return a cleanup function when required. |
| Unnecessary effects |
Creates extra complexity and possibly extra renders. |
Calculate derived values directly. |
| Unnecessary memoization |
Makes code harder to maintain without clear benefit. |
Memoize only when useful. |
| Incorrect useRef expectations |
Ref changes do not trigger re-renders. |
Use state for values displayed in the UI. |
19. Best Practices for React Hooks
- Follow the Rules of Hooks and call regular Hooks only at the top level of React function components or custom Hooks.
- Use meaningful names for state variables and custom Hooks.
- Update state through setter functions.
- Use functional state updates when the next value depends on the previous value.
- Include all required reactive values in effect dependency arrays.
- Use cleanup functions for timers, subscriptions, event listeners, and connections when needed.
- Avoid using effects for calculations that can be performed during rendering.
- Use
useMemo and useCallback when they offer a practical benefit.
- Keep state objects and arrays immutable.
- Use the React Hooks lint rules to identify common mistakes.
- Test components with React Strict Mode enabled during development.
20. Practical Project: Counter Using React Hooks
Create a counter application that demonstrates correct use of useState, event handlers, and functional state updates.
Requirements
- Display the current count.
- Provide an Increase button.
- Provide a Decrease button.
- Provide a Reset button.
- Use the state setter to update the count.
- Avoid unnecessary effects.
Complete Code
import { useState } from "react";
function CounterApp() {
const [count, setCount] = useState(0);
const increase = () => {
setCount(current => current + 1);
};
const decrease = () => {
setCount(current => current - 1);
};
const reset = () => {
setCount(0);
};
return (
<div>
<h1>React Counter</h1>
<p>Current Count: {count}</p>
<button onClick={increase}>Increase</button>
<button onClick={decrease}>Decrease</button>
<button onClick={reset}>Reset</button>
</div>
);
}
export default CounterApp;
Learning outcome: This project demonstrates how to declare a Hook correctly, update state without direct mutation, and handle user interactions without unnecessary effects.
21. React Hooks Interview Questions and Answers
Q1. What are React Hooks?
Hooks are functions that allow functional components to use React features such as state, effects, context, and references.
Q2. What are the Rules of Hooks?
Regular Hooks must be called at the top level of React function components or custom Hooks, not inside loops, conditions, or nested functions.
Q3. Why should we not call Hooks conditionally?
React relies on a consistent order of Hook calls to associate state and other Hook data with the correct component logic.
Q4. Why should we not mutate state directly?
State is an immutable snapshot for each render. Direct mutation can prevent React from detecting updates as expected and can cause inconsistent UI behavior.
Q5. What is a stale closure in React?
A stale closure occurs when a callback or effect uses values captured from an earlier render when newer values are expected.
Q6. What is the purpose of the useEffect dependency array?
It identifies the reactive values that determine when an effect should synchronize again.
Q7. Why can useEffect cause an infinite loop?
An effect can loop when it updates state and that update changes one of the effect's dependencies, triggering another run.
Q8. Why is cleanup important in useEffect?
Cleanup releases external resources such as timers, subscriptions, event listeners, and connections when they are no longer needed.
Q9. What is the difference between useState and useRef?
useState stores data that can trigger a re-render when updated. useRef stores a persistent mutable reference whose changes do not trigger a re-render.
Q10. When should useMemo be used?
useMemo can cache an expensive calculation when its dependencies remain unchanged. It is a performance optimization and is not necessary for every calculation.
Q11. Can Hooks be used inside custom Hooks?
Yes. Custom Hooks can call other Hooks, provided they follow the Rules of Hooks.
Q12. How can developers prevent common Hook mistakes?
Follow the Rules of Hooks, update state immutably, declare correct dependencies, clean up external resources, and use the React Hooks lint rules.
22. Practice Exercises
- Create a counter using
useState without directly modifying the state variable.
- Write a component that calls Hooks correctly when a user is logged in or logged out.
- Render a list of products using separate components and stable keys.
- Fix an effect that uses state but has an empty dependency array.
- Find and correct an infinite loop caused by an effect updating its own dependency.
- Create a timer with a cleanup function.
- Calculate a product total directly from price and quantity without unnecessary state.
- Create a custom Hook that manages a username.
- Compare
useState and useRef in a small interactive application.
- Enable React Strict Mode and verify that effects handle setup and cleanup correctly.
23. Quick Revision Checklist
- Understand the purpose of React Hooks.
- Call Hooks at the top level of components or custom Hooks.
- Avoid calling Hooks inside conditions, loops, or event handlers.
- Use state setter functions instead of direct mutation.
- Use functional updates when the next state depends on the previous state.
- Declare correct effect dependencies.
- Prevent unnecessary effect loops.
- Clean up timers, subscriptions, and event listeners.
- Use state for values that affect displayed UI.
- Use memoization only when it is beneficial.
- Follow lint recommendations and test your components.
24. Conclusion
Common Hook mistakes can lead to incorrect state updates, stale values, infinite loops, memory leaks, and unexpected rendering behavior. Developers can avoid these problems by following the Rules of Hooks, updating state correctly, declaring dependencies accurately, and cleaning up external resources. Writing simple components and using effects only when needed makes React applications easier to understand, debug, and maintain.
Official React Resources: Rules of Hooks | Removing Effect Dependencies | Components and Hooks Must Be Pure
Explore React JS Training: JustAcademy React JS Training Course | Register for React JS Course Demo