✓Node.js 18+, React 18+, basic understanding of function components and state, familiarity with JavaScript ES6+ (arrow functions, destructuring, spread operator).
⚡Quick Answer
React Hooks Rules and Best Practices: A core React concept for building modern user interfaces. It helps you structure your components efficiently and handle data flow predictably.
✦ Definition~90s read
What is React Hooks Rules and?
React Hooks Rules and Best Practices is a fundamental concept in React development. It refers to the patterns, APIs, and best practices that React provides for building user interfaces. Understanding this concept is essential for writing clean, efficient, and maintainable React code. This tutorial covers everything from basic usage to advanced patterns with real-world examples.
★
React is a JavaScript library for building user interfaces.
Plain-English First
React is a JavaScript library for building user interfaces. This article covers hooks rules — a key concept for building modern web applications.
⚙ Browser compatibility
Latest versions — ✓ supported
Chrome
Firefox
Safari
Edge
✓
✓
✓
✓
A comprehensive guide to react hooks rules and best practices with production examples and best practices.
React Hooks are functions that let you use state and lifecycle features from function components. They are governed by two core rules: only call Hooks at the top level of your component, and only call Hooks from React functions. These rules are not arbitrary—they ensure that the order of Hook calls is consistent across renders. React relies on this order to correctly associate state and effects with each Hook. Violating these rules leads to subtle bugs like stale state, infinite loops, or crashes. For example, calling a Hook inside a condition can shift the call order, causing React to misassign state. Understanding why these rules exist helps you write predictable components and debug issues faster.
BadHookOrder.jsxJSX
1
2
3
4
5
6
7
8
9
import { useState } from 'react';
export default function BadComponent({ flag }) {
if (flag) {
const [count, setCount] = useState(0); // ❌ Conditional hook
}
const [name, setName] = useState('');
return <div>{name}</div>;
}
Output
Error: React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render.
Conditional Hooks are the #1 cause of 'Rendered fewer hooks than expected' errors. Always move conditions inside the Hook or use early returns before any Hook calls.
📊 Production Insight
In production, a conditional hook can cause a component to crash silently on one render path while working on another, making it extremely hard to reproduce and debug.
🎯 Key Takeaway
Hook call order must be identical across every render to maintain state consistency.
thecodeforge.io
React Hooks Rules
The Rules of Hooks: Top-Level Only
The first rule of Hooks is to call them at the top level of your React function, not inside loops, conditions, or nested functions. This ensures that Hooks are called in the same order each time a component renders. React uses a linked list of Hook nodes internally; the order of calls determines which state belongs to which Hook. If you break this order, React's internal state mapping becomes corrupted. For example, moving a useState inside a loop will cause the number of Hooks to change between renders, leading to errors. Always place all Hook calls at the top of your component, before any early returns.
You can have early returns, but only after all Hook calls. Place your Hooks first, then conditional rendering logic.
📊 Production Insight
A common production bug is adding a guard clause before Hooks, which causes 'Rendered fewer hooks than expected' errors when the guard triggers.
🎯 Key Takeaway
Always call Hooks at the top level of your component, before any early returns.
Only Call Hooks from React Functions
The second rule restricts Hook calls to React function components and custom Hooks. You cannot call Hooks from regular JavaScript functions, class components, or event handlers. This ensures that Hooks are only used within the React functional paradigm, where the framework can manage their lifecycle. Custom Hooks are the only exception—they are JavaScript functions whose name starts with 'use' and that call other Hooks. This rule prevents misuse like calling useState inside a utility function, which would break the component's state management. Always ensure your custom Hooks follow the naming convention and only call Hooks at their top level.
React's lint rules rely on the 'use' prefix to detect Hook calls inside custom Hooks. Without it, you'll get lint errors and potential runtime issues.
📊 Production Insight
A developer once called useState inside a debounced utility function, causing state updates to be lost. The fix was to move the Hook into a custom Hook and call it from the component.
🎯 Key Takeaway
Hooks can only be called from React function components or custom Hooks that start with 'use'.
thecodeforge.io
React Hooks Rules
The Dependency Array: Your Safety Net
useEffect, useMemo, and useCallback accept a dependency array that tells React when to re-run the effect or recompute the value. Omitting dependencies or including stale values leads to bugs like infinite loops or missing updates. The rule is simple: include every reactive value (props, state, derived values) used inside the effect or memo. The exhaustive-deps ESLint rule enforces this. However, you may intentionally omit a dependency if you want to run an effect only on mount (empty array). Be cautious: stale closures are a common source of bugs. Use the functional update form of setState when the new state depends on the old state to avoid unnecessary dependencies.
DependencyArray.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useState, useEffect } from 'react';
export default function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
if (!query) return;
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, [query]); // ✅ query is included
return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
}
If you omit a dependency, your effect will capture the initial value and never update. Always run the linter and fix warnings.
📊 Production Insight
A missing dependency in a useEffect caused a dashboard to show stale data for hours. The fix was adding the missing prop to the dependency array, which triggered a re-fetch on prop change.
🎯 Key Takeaway
Include every reactive value used inside useEffect, useMemo, or useCallback in the dependency array.
useState: Immutable Updates and Functional Updates
State in React must be updated immutably. For objects and arrays, always create a new reference instead of mutating the existing state. Use the spread operator or methods like map/filter that return new arrays. When the new state depends on the previous state, use the functional update form: setCount(prev => prev + 1). This avoids stale state issues, especially in concurrent mode or when multiple state updates are batched. Functional updates are also safer inside useEffect dependencies because they don't require the previous state to be listed as a dependency.
When your new state depends on the old state, use the functional form. It's more predictable and avoids dependency issues in effects.
📊 Production Insight
A mutation of a nested object in state caused a component to not re-render, leading to a UI that appeared stuck. The fix was spreading the object at every level of nesting.
🎯 Key Takeaway
Never mutate state directly; use functional updates when the new state depends on the previous state.
useEffect: Side Effects and Cleanup
useEffect is the primary hook for side effects: data fetching, subscriptions, timers, and manual DOM manipulations. Always return a cleanup function to avoid memory leaks and race conditions. For example, when fetching data, use an abort controller or ignore stale responses. Cleanup runs on unmount and before re-running the effect. This is crucial for subscriptions like WebSockets or event listeners. Also, avoid async functions directly inside useEffect; instead, define an async function inside and call it.
Without cleanup, a fast component remount could resolve an old fetch after a new one, causing stale state. Always abort or ignore stale responses.
📊 Production Insight
A missing cleanup in a WebSocket subscription caused memory leaks and duplicate connections in production, eventually crashing the browser tab.
🎯 Key Takeaway
Always return a cleanup function from useEffect to cancel subscriptions and async operations.
useMemo and useCallback: When to Memoize
useMemo memoizes a computed value, and useCallback memoizes a function. They are optimization tools, not guarantees. Use them only when the computation is expensive (e.g., large arrays, complex calculations) or when the value/function is passed to a child component that uses React.memo. Premature memoization adds complexity and can even hurt performance by increasing memory usage. Profile first, then memoize. Also, useMemo should not be used for side effects; that's what useEffect is for. Remember that useMemo and useCallback have their own dependency arrays, which must be correct.
Memoization has overhead. Only use it when you have measured a performance bottleneck. Premature optimization can make code harder to read.
📊 Production Insight
A team added useMemo to every computed value, causing a 20% memory increase. Profiling showed only one computation was expensive; removing the rest improved performance.
🎯 Key Takeaway
Use useMemo and useCallback only for expensive computations or to preserve referential equality for child components.
Custom Hooks: Extract and Reuse Logic
Custom Hooks are the primary way to reuse stateful logic across components. They are JavaScript functions that call other Hooks and follow the same rules. Name them with a 'use' prefix to enable linting. Extract logic that involves multiple Hooks or complex side effects into a custom Hook. This improves readability and testability. For example, a useFetch hook can encapsulate loading, error, and data states. Custom Hooks can also accept arguments and return values. They compose naturally, allowing you to build complex behavior from simple pieces.
They can call other custom Hooks, accept parameters, and return anything. Use them to abstract complex logic and keep components clean.
📊 Production Insight
A codebase with 50+ components all had duplicated fetch logic. Extracting a useFetch hook reduced bugs by 30% and made error handling consistent.
🎯 Key Takeaway
Extract reusable stateful logic into custom Hooks to improve code organization and reusability.
Testing Hooks: Best Practices
Testing Hooks requires a React environment. Use @testing-library/react-hooks or renderHook from React's test utilities. Test the hook's behavior by calling it inside a test component. For custom Hooks, test the returned values and side effects. Mock external dependencies like fetch. Ensure that state updates and effects run correctly. Also test edge cases: missing dependencies, cleanup, and error states. Avoid testing implementation details; focus on the hook's contract (inputs and outputs).
Don't test internal state directly; test what the hook returns and how it affects the component. This makes tests resilient to refactoring.
📊 Production Insight
A custom hook for authentication had a bug where the token wasn't cleared on logout. The test caught it because the hook returned a non-null token after logout.
🎯 Key Takeaway
Use renderHook to test custom Hooks in isolation, focusing on inputs and outputs.
Common Pitfalls and How to Avoid Them
Even experienced developers fall into Hook traps. Stale closures: when an effect captures an old value because the dependency array is missing. Infinite loops: when an effect updates state that is in its dependency array. Over-fetching: when useEffect runs on every render due to missing dependencies. State mutation: directly modifying state objects. Mixing Hooks with class components: you cannot use Hooks inside class components. To avoid these, always use the exhaustive-deps ESLint plugin, prefer functional updates, and keep state minimal. Profile before optimizing.
InfiniteLoop.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // ❌ count is in deps, causes infinite loop
}, [count]);
return <p>{count}</p>;
}
Never update state inside useEffect if that state is in the dependency array. Use functional updates or remove the dependency if possible.
📊 Production Insight
An infinite loop caused by a missing dependency in useEffect brought down a production server by exhausting CPU. The fix was adding the missing dependency and using functional update.
🎯 Key Takeaway
Watch out for stale closures, infinite loops, and state mutations; use linting and best practices to avoid them.
Performance Optimization with Hooks
React is fast by default, but you can optimize with Hooks. Use React.memo to prevent unnecessary re-renders of child components when props haven't changed. Combine with useCallback and useMemo to preserve referential equality. Avoid creating new objects or arrays in render that are passed as props. Use useRef for mutable values that don't need to trigger re-renders. For large lists, use virtualization (e.g., react-window). Always measure performance with React DevTools Profiler before optimizing.
Use React DevTools Profiler to identify bottlenecks. Premature optimization adds complexity without measurable benefit.
📊 Production Insight
A team added React.memo to every component, causing memory bloat. Profiling revealed only one list component was slow; memoizing it solved the issue.
🎯 Key Takeaway
Optimize with React.memo, useCallback, and useMemo only after profiling shows a performance issue.
Migrating from Class Components to Hooks
Migrating class components to Hooks can improve code readability and reduce bundle size. Start by identifying state and lifecycle methods. Replace this.state and this.setState with useState. Replace componentDidMount, componentDidUpdate, and componentWillUnmount with useEffect. Use useRef for instance variables. Convert lifecycle logic into multiple useEffect calls if needed. Test thoroughly after migration. Hooks allow you to extract logic into custom Hooks, making the code more modular. However, don't migrate everything at once; do it incrementally.
You can use Hooks in new components while keeping existing class components. No need for a full rewrite.
📊 Production Insight
A gradual migration from classes to Hooks reduced the bundle size by 15% and improved developer velocity, but required careful testing of side effects.
🎯 Key Takeaway
Replace class lifecycle methods with useEffect and state with useState; extract logic into custom Hooks.
React Compiler and Hook Rules
The React Compiler (formerly known as React Forget) is a new tool that automatically memoizes components and hooks, reducing the need for manual useMemo, useCallback, and React.memo. However, it does not change the fundamental rules of hooks. The compiler still requires hooks to be called at the top level of React functions and only from React functions. The compiler enforces these rules during compilation and will throw errors if violations are detected. For example, calling a hook inside a condition or loop will still be flagged. The compiler's memoization optimizations are applied after the rules are satisfied, meaning you must still follow the rules of hooks. In practice, the compiler can eliminate many performance pitfalls, but developers must ensure their code adheres to the rules to benefit from compilation. A common misconception is that the compiler allows breaking hook rules—it does not. The compiler simply automates memoization, not the structural constraints of hooks.
CompilerHookRules.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ❌ This will still cause a compiler error
function MyComponent({ flag }) {
if (flag) {
const [value, setValue] = useState(0); // Error: Hook called conditionally
}
return <div />;
}
// ✅ This is valid and can be optimized by the compiler
function MyComponent({ flag }) {
const [value, setValue] = useState(0);
useEffect(() => {
// side effect
}, []);
return <div>{flag ? value : null}</div>;
}
When using the React Compiler, you can remove manual useMemo and useCallback calls, but keep the ESLint react-hooks plugin enabled to catch rule violations early. The compiler will optimize performance, but structural errors remain your responsibility.
🎯 Key Takeaway
The React Compiler automates memoization but does not relax the rules of hooks; you must still follow them for your code to compile and run correctly.
Server Components and Hooks
React Server Components (RSC) run on the server and do not have access to client-side features like state, effects, or browser APIs. Therefore, hooks such as useState, useEffect, useReducer, useRef, and custom hooks that use them are unavailable in Server Components. Only hooks that are purely computational and do not rely on client-side interactivity can be used, such as useId (for generating unique IDs) and use (for reading context, but only in limited scenarios). If you attempt to use a client-side hook in a Server Component, React will throw an error. To use hooks, you must mark the component with 'use client' directive, making it a Client Component. This distinction is crucial for performance: Server Components reduce bundle size and improve initial load, but they cannot handle interactivity. When designing your app, separate server-only data fetching and static rendering from client-side interactive logic. For example, a Server Component can fetch data and pass it to a Client Component that uses hooks for interactivity.
ServerComponentExample.jsxJSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ServerComponent.server.jsx (no 'use client')
export default function ServerComponent() {
// ❌ Cannot use hooks like useState here
// const [count, setCount] = useState(0); // Errorreturn <div>Static content</div>;
}
// ClientComponent.client.jsx ('use client')
'use client';
import { useState } from 'react';
export default function ClientComponent({ data }) {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c+1)}>Count: {count}</button>;
}
In production, keep Server Components for data fetching and static content to reduce bundle size. Use Client Components sparingly for interactive parts. This pattern improves initial load time and SEO.
🎯 Key Takeaway
React Server Components cannot use most hooks; only Client Components (marked with 'use client') can use hooks like useState and useEffect.
useMemo vs useCallbackWhen to memoize values vs functionsuseMemouseCallbackPurposeMemoize a computed valueMemoize a function referenceReturn TypeValue (any type)FunctionUse CaseExpensive calculations, derived dataPassing callbacks to child componentsDependenciesArray of values the computation depends Array of values the function closure depPerformance ImpactAvoids re-computation on re-rendersPrevents unnecessary re-renders of childTHECODEFORGE.IO
thecodeforge.io
React Hooks Rules
useReducer vs useState Decision Tree
Choosing between useState and useReducer depends on the complexity of your state logic. useState is ideal for simple, independent state values like a toggle, a counter, or a form field. useReducer is better when state transitions involve multiple sub-values or when the next state depends on the previous state in complex ways. A decision tree: (1) Is your state a single primitive value? Use useState. (2) Does your state have multiple related values that change together? Consider useReducer. (3) Do you have complex state logic with multiple conditions or actions? Use useReducer. (4) Do you need to pass state update logic down to child components without callbacks? useReducer's dispatch is stable and can be passed directly. (5) Are you dealing with deeply nested state? useReducer with Immer can simplify updates. For example, a form with multiple fields is a good candidate for useReducer because you can handle all field updates with a single reducer. In contrast, a simple counter is fine with useState. Performance-wise, useReducer can avoid unnecessary re-renders if the reducer is pure and the dispatch function is stable. However, for most cases, useState is simpler and sufficient. Start with useState and refactor to useReducer when you find yourself writing multiple useState calls that are tightly coupled.
In production, useReducer can improve performance by providing a stable dispatch function and reducing re-renders when state logic is complex. However, avoid premature optimization; useState is often sufficient and easier to debug.
🎯 Key Takeaway
Use useState for simple independent state and useReducer for complex state logic with multiple sub-values or actions; start with useState and refactor to useReducer as needed.
⚙ Quick Reference
15 commands from this guide
File
Command / Code
Purpose
BadHookOrder.jsx
export default function BadComponent({ flag }) {
Why React Hooks Rules Exist
TopLevelHooks.jsx
export default function UserProfile({ userId }) {
The Rules of Hooks
CustomHook.jsx
function useWindowWidth() {
Only Call Hooks from React Functions
DependencyArray.jsx
export default function SearchResults({ query }) {
The Dependency Array
ImmutableState.jsx
export default function TodoList() {
useState
EffectCleanup.jsx
export default function UserStatus({ userId }) {
useEffect
Memoization.jsx
function ExpensiveList({ items, filter }) {
useMemo and useCallback
useFetch.js
export function useFetch(url) {
Custom Hooks
useFetch.test.js
global.fetch = jest.fn(() =>
Testing Hooks
InfiniteLoop.jsx
export default function Counter() {
Common Pitfalls and How to Avoid Them
OptimizedList.jsx
const ListItem = memo(({ item, onDelete }) => {
Performance Optimization with Hooks
ClassToHook.jsx
class Timer extends React.Component {
Migrating from Class Components to Hooks
CompilerHookRules.jsx
function MyComponent({ flag }) {
React Compiler and Hook Rules
ServerComponentExample.jsx
export default function ServerComponent() {
Server Components and Hooks
ReducerVsState.jsx
const [count, setCount] = useState(0);
useReducer vs useState Decision Tree
Key takeaways
1
Hook Call Order
Always call Hooks at the top level of your component, never inside conditions or loops, to ensure consistent state mapping across renders.
2
Dependency Arrays
Include every reactive value used inside useEffect, useMemo, or useCallback to avoid stale closures and missing updates.
3
Custom Hooks
Extract reusable stateful logic into custom Hooks (prefix with 'use') to improve code organization and testability.
4
Optimize with Caution
Use React.memo, useMemo, and useCallback only after profiling confirms a performance bottleneck; premature optimization adds complexity.
Common mistakes to avoid
3 patterns
×
Not understanding React component re-rendering
Fix
Use React.memo, useMemo, and useCallback strategically to prevent unnecessary re-renders.
×
Ignoring the rules of hooks
Fix
Always call hooks at the top level, not inside conditions, loops, or callbacks.
×
Mutating state directly instead of using setState
Fix
Always use the state setter function and treat state as immutable.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the Virtual DOM and how does React use it?
Q02JUNIOR
Explain the difference between state and props.
Q03JUNIOR
What is the purpose of the useEffect hook?
Q04JUNIOR
How does React handle keys in lists?
Q01 of 04JUNIOR
What is the Virtual DOM and how does React use it?
ANSWER
The Virtual DOM is a lightweight JavaScript representation of the real DOM. React diffs the Virtual DOM with the previous version and applies only the changed parts to the real DOM.
Q02 of 04JUNIOR
Explain the difference between state and props.
ANSWER
Props are read-only data passed from parent to child. State is mutable data managed within a component. Changes to state trigger re-renders.
Q03 of 04JUNIOR
What is the purpose of the useEffect hook?
ANSWER
useEffect handles side effects in functional components: data fetching, subscriptions, DOM manipulation, and timers. It replaces lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount.
Q04 of 04JUNIOR
How does React handle keys in lists?
ANSWER
Keys help React identify which items have changed, been added, or removed. Use stable, unique IDs (not array indices) as keys for optimal performance.
01
What is the Virtual DOM and how does React use it?
JUNIOR
02
Explain the difference between state and props.
JUNIOR
03
What is the purpose of the useEffect hook?
JUNIOR
04
How does React handle keys in lists?
JUNIOR
FAQ · 6 QUESTIONS
Frequently Asked Questions
01
Can I call Hooks inside if statements?
No. Hooks must be called at the top level of your component, not inside conditions, loops, or nested functions. This ensures consistent call order across renders.
Was this helpful?
02
What happens if I miss a dependency in useEffect?
The effect will capture stale values from the initial render and may not re-run when that dependency changes. This leads to bugs like stale data or missing updates. Always include all reactive values used inside the effect.
Was this helpful?
03
How do I test a custom Hook?
Use @testing-library/react-hooks or React's renderHook utility. Call the hook inside a test component and assert on the returned values. Mock external dependencies like fetch.
Was this helpful?
04
When should I use useMemo vs useCallback?
useMemo memoizes a computed value, useCallback memoizes a function. Use them when the computation is expensive or when passing the value/function to a child component wrapped in React.memo. Profile first to confirm a bottleneck.
Was this helpful?
05
Can I use Hooks inside class components?
No. Hooks are only available in function components. You cannot call them inside class components. To use Hooks, convert the class to a function component.
Was this helpful?
06
Why does my useEffect run infinitely?
This usually happens when the effect updates a state variable that is listed in its dependency array, causing a loop. Use functional updates or remove the state from dependencies if possible.