See how React manages hooks as a linked list, processes state updates, and re-renders.
Hooks are stored as a singly linked list on the fiber node. When your component renders, React walks this list in order β matching each hook call (useState, useEffect, useRef, etc.) to its stored state. This is why the Rules of Hooks exist: if you call hooks conditionally, the list order changes and React reads the wrong state for the wrong hook.
Each hook call (useState, useEffect, useRef, useMemoβ¦) creates a 'Hook' object with a memoizedState field and a next pointer. React appends each one to the list in call order. The list is stored on the fiber node's memoizedState field.
A useState hook object stores: memoizedState (current value), queue (linked list of pending setState calls), and dispatch (the setState function you use in your component). The dispatch function is stable β it never changes between renders.
When you call setState(newValue), React does not immediately update the state. Instead, it appends an Update object to the hook's queue and schedules a re-render of the fiber. Multiple setState calls in one event handler get batched into one re-render.
On the next render, React walks the hooks list again. For each useState hook, it processes all queued updates by running them in order (or calling the reducer function for useReducer). The final computed value becomes the new memoizedState.
An effect hook object stores: create (the callback you passed), destroy (the cleanup function it returned last time), deps (the dependency array), and a tag indicating whether it's a useEffect, useLayoutEffect, or useInsertionEffect.
Hooks are stored as a singly linked list on the fiber node. Call order = list order. Conditional hooks break the list.
The field on a Hook object that stores its current value. For useState: the state value. For useRef: '{' current: value '}'. For useMemo: the cached result.
A circular linked list of setState calls waiting to be processed on the next render. Multiple updates are batched into one queue flush.
React swaps the hook implementation depending on render phase. During first render: HooksDispatcherOnMount. During re-renders: HooksDispatcherOnUpdate. This is why hooks can't be called outside components.
Call hooks only at the top level (never inside conditionals/loops) and only from React functions. These rules exist because React identifies hooks by their position in the linked list.
1// Your component:2function Counter() {3 const [count, setCount] = useState(0); // Hook 14 const [name, setName] = useState('Bob'); // Hook 25 const ref = useRef(null); // Hook 367 useEffect(() => { ... }, [count]); // Hook 48}910// React's internal hooks linked list for this fiber:11Hook1 { memoizedState: 0, next: Hook2 } // count12Hook2 { memoizedState: 'Bob', next: Hook3 } // name13Hook3 { memoizedState: { current: null }, next: Hook4 } // ref14Hook4 { memoizedState: { create, destroy, deps: [0] }, next: null }1516// If you conditionally call hook 2:17// Hook1 β Hook3 β Hook4 (Hook2 slot is now useRef!)18// React reads "Bob" as the ref's current value β BUG
Understanding the hooks internals explains why the Rules of Hooks are not arbitrary restrictions but technical necessities. It also explains why the useState setter function is always stable (it's attached to the queue, not the value), why hooks work across renders (they're persisted on the fiber), and why you can build custom hooks β they're just functions that call other hooks, adding more nodes to the list.
You add an early return in a component for a loading state: `if (loading) return <Spinner />`. After the data loads, React crashes with 'Rendered more hooks than during the previous render.'
The early return is ABOVE some hook calls. On the first render (loading=true), React processes 2 hooks. On the second render (loading=false), it processes 5 hooks. React's linked list has 2 slots but encounters 5 hook calls β the list is corrupted.
Move ALL hook calls above any conditional returns. The early return must come AFTER the last hook call. React hooks are a linked list indexed by call order β the call count must be identical across every render of the same component.
Takeaway: The 'Rules of Hooks' aren't arbitrary restrictions β they're a direct consequence of the linked list data structure. React identifies hooks by position (call order), not by name. Changing the number of hooks between renders corrupts the list.
You build a custom `useToggle` hook used in 3 different components. You notice that toggling in one component doesn't affect the others β but you're confused about WHY, since they share the same hook code.
Developers sometimes assume custom hooks share state like a global singleton. They expect `useToggle()` in Component A and Component B to share the same boolean value, leading to incorrect architecture decisions.
Each component has its own fiber node with its own hooks linked list. When Component A calls `useToggle()`, React creates a hook node in Component A's list. Component B gets a completely separate hook node in its own list. Custom hooks are just a code-sharing mechanism β state is always local to the calling component's fiber.
Takeaway: Custom hooks share LOGIC, not STATE. Each component instance gets its own copy of the hook's state because each fiber maintains its own hooks linked list. To share state between components, you need Context, a state manager, or lifting state up.