Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 56

Understanding React's Rules of Hooks

Recommended resources

Introduction

Welcome to the first lesson of our new module, "Mastering React Hooks." In the previous module, we concluded by exploring React's unidirectional data flow, focusing on how data is passed down through props and how child components communicate upwards by invoking callback functions. This established the patterns for managing data between components.

Now, we shift our focus to managing state within a component. This is where Hooks come in. They are functions that let you "hook into" React state and lifecycle features from function components.

This lesson addresses a crucial starting point: Explain the Rules of Hooks and why they are necessary for React to associate state with the correct component.

Instead of just memorizing rules, we're going to follow your preference for conceptual depth and look under the hood. By the end of this lesson, you will understand the internal data structures and logic that make these rules a fundamental necessity for React's design. This knowledge is key to avoiding common bugs and writing more robust React code.


The Two Rules of Hooks

Before diving into the "why," let's clearly state the "what." React has two primary rules for using hooks.

Rules of Hooks Summary
This diagram provides a concise summary of the two main Rules of Hooks and an additional guideline for `useEffect`.
  1. Only Call Hooks at the Top Level. Do not call Hooks inside loops, conditions, or nested functions. Hooks must be called in the same order each time a component renders.
  2. Only Call Hooks from React Functions. You should only call Hooks from React function components or from custom Hooks (which we'll cover later). You cannot call them from regular JavaScript functions.

These rules might seem restrictive, but they are essential. Let's explore the underlying mechanism that necessitates them.


The "Why": How React Associates State with Hooks

The key to understanding the Rules of Hooks is to understand how React keeps track of a hook's state between renders. How does React know that a specific useState call corresponds to the same piece of state every single time the component re-renders?

The answer is surprisingly simple: React relies on the order in which Hooks are called.

To make this tangible, we'll explore a simplified, "from-scratch" implementation of useState. This exercise reveals the core logic that dictates the rules.

How do React Hooks really work? Let's build useState from scratch!

This video by Jacques Blom provides an excellent walkthrough of building a useState hook from scratch. It perfectly demonstrates the core problem of persisting state across renders and how an array-and-index approach solves it.

Watch from 00:39 to 09:45, and then from 11:32 to 12:49. Pay close attention to these key moments: (00:39 - 05:25): The initial challenge of making a single state value persist across renders. (05:25 - 09:45): This is the most critical part. Observe how the implementation breaks when a second useState is introduced and how this is fixed by using an array (stateValues) and a callIndex. This directly explains the dependency on call order. (11:32 - 12:49): The presenter explicitly connects this implementation back to the official Rules of Hooks, explaining why call order is not just a suggestion but a requirement.

As the video demonstrates, a simplified mental model for React's hook management within a component is:

  1. An array is used to store the state values for all hooks.
  2. A counter (or index pointer) keeps track of which hook is currently being called.
  3. On the first render, useState pushes a value into the array and increments the index.
  4. On subsequent renders, the index is reset to zero. Each useState call retrieves its state from the array at the current index and then increments the index for the next hook.

This positional indexing is precisely why the call order must be identical on every render. If a hook were called conditionally, the indices would get misaligned, and hooks would retrieve the wrong state, leading to unpredictable bugs.


From a Simple Model to React's Actual Architecture

The array-and-global-index model is a fantastic conceptual tool. React's actual implementation is, as you might expect, more sophisticated but follows the exact same principle. Let's upgrade our mental model to be closer to React's real architecture.

Instead of a single global array, React uses an internal data structure called a Fiber tree. Each component instance in your app corresponds to a fiber node. This node is an object that holds all the information about a component, including its props, its place in the component tree, and—crucially for us—its state.

How useState Works Under the Hood

This article from MyDevFlow offers a clear explanation of how hooks are tied to React's Fiber architecture. It will connect our simplified array model to the real-world implementation.

Read the sections 'The Foundation: React's Fiber Architecture' and 'Hook Registration and State Association', including the FAQ 'Why do hook calls need to be in the same order every time?'. Focus on these key concepts: How each component instance is a 'fiber node'. The memoizedState property on the fiber node. How memoizedState stores hooks as a linked list, not an array. Why this linked list structure still relies on positional indexing, making the 'Hook Call Order Dependency' critical.

So, let's refine our understanding:

  • React doesn't use a global array. Instead, state for each component is stored within that component's fiber node.
  • The states are not stored in an array but in a linked list attached to the fiber node's memoizedState property.
  • During rendering, React maintains a pointer to the current hook in this linked list. Each time you call a hook, React returns the state from the current hook object and advances the pointer to the next one.

Despite the data structure being a linked list instead of an array, the core principle is identical: the system is based on positional indexing.


What Happens When the Rules Are Broken?

Let's trace the failure case. Imagine you have a component with a hook inside a condition:

function MyComponent({ showExtraField }) {
  const [name, setName] = useState("Guest"); // Hook 1

  let a, b;
  if (showExtraField) {
    [a, setA] = useState("A"); // Hook 2
  }

  [b, setB] = useState("B"); // Hook 3 (or 2, depending on the condition)
  
  // ...
}

Let's walk through two renders:

Render #1: showExtraField is true

  1. useState("Guest") is called. React creates the first link in the memoizedState list: { memoizedState: 'Guest', next: ... }.
  2. useState("A") is called. React creates the second link: { memoizedState: 'A', next: ... }.
  3. useState("B") is called. React creates the third link: { memoizedState: 'B', next: null }.

The component's state list looks like: ['Guest'] -> ['A'] -> ['B']

Render #2: A user action sets showExtraField to false

  1. The component re-renders. React resets its internal hook pointer to the start of the list.
  2. useState("Guest") is called. It correctly reads from the first link and gets 'Guest'. The pointer advances.
  3. The if condition is false. The call to useState("A") is skipped.
  4. useState("B") is called. This is the second hook call in this render. React looks at the second link in the state list, which contains the value 'A'.
    • Bug: The b state variable is now 'A', not 'B'.
    • React throws an error because the number of hooks called on this render doesn't match the previous render.

This state corruption is exactly what the first rule of hooks prevents. The article "Deep Dive on React Hooks Architecture" has excellent diagrams that visualize this exact scenario under its "React Hook Rules and Mechanisms" section, showing how the memoizedState lookup fails when the call order changes.


The Second Rule: "Only Call Hooks from React Functions"

This rule is about context. For the positional indexing to work, React needs to know which component's state list (which fiber node) to look at.

When React starts rendering a function component, it sets a global-like variable, currentlyRenderingFiber, to point to that component's fiber node. When a hook like useState is called, it reads this variable to know which component it belongs to and where to find its linked list of states.

If you call a hook from a regular JavaScript function outside of a component's render cycle, currentlyRenderingFiber would be null. The hook wouldn't have a component context to attach its state to, and the whole system would break down.


Conclusion

In this lesson, we established that the Rules of Hooks are not arbitrary constraints but are direct consequences of React's internal design.

Key Takeaways:

  • The Two Rules: Always call hooks at the top level of your React function, and only call them from React functions or custom hooks.
  • The Core Mechanism: React uses positional indexing to associate state with hook calls between renders.
  • The Internal Structure: State for a component's hooks is stored as a linked list in the memoizedState property of its corresponding fiber node.
  • Why the Rules are Necessary: Violating the rules breaks the stable call order, causing React to misidentify hooks with their state, leading to bugs and state corruption. Hooks also require the context of a rendering component to function correctly.

Understanding this "why" demystifies hooks and empowers you to use them correctly and confidently.


What's Next?

Now that we have a solid understanding of the rules that govern all hooks, our next lesson will be a deep dive into the most common hook of all: useState. We will explore how it manages component state, how it triggers re-renders, and the nuances of its update mechanism.