Why does hook call order matter? The Rules of React Hooks explained.

Harris Ferguson
7shifts Back of House
5 min readSep 26, 2023
Photo by Anne Nygård on Unsplash

Understanding the underlying mechanisms of React Hooks is paramount for any front-end developer aiming to master the React framework. While hooks introduce a world of possibilities for function components, they also come with a set of rules.

A Sticky Situation

Hooks are very useful for fetching data. Sometimes, those data sources are related and we might want to do something like the below snippet inside of a function component:

const { data, loading } = useGetData();
const userId = data.id;
const { relatedData } = useGetRelatedDataForUser(userId);

The problem here is that userId = data.id might not be defined yet. You might be tempted to check that loading is false before making the second call:

const { data, loading } = useGetData();
if (!loading){
const userId = data.id;
const { relatedData } = useGetRelatedDataForUser(userId);
}

However, if you try something like this your console will display an error that you have violated the Rules of Hooks. The rules of hooks are:

  1. Call hooks only at the top level of a function component
  2. Call hooks only at the top level of another hook

Note here that a hook is any function whose name starts with the string use. This means:

  1. You cannot call a hook inside a condition or loop
  2. You cannot call a hook after a conditional return
  3. You cannot call a hook in a Class component

The reason behind these rules is that the order each hook is called needs to stay exactly the same each time a component is called.

Why Call Order Matters

React relies on the order of the hook calls to sync up with the state and lifecycle of your component. Each hook call is associated with a particular piece of state or lifecycle, and React maintains a running order of these hooks.

Each Hook call is associated with a node in a linked list that react keeps track of per component. This node stores the state value and a pointer to the next node (or Hook).

For instance, consider a React component with two state variables, ‘name’ and ‘age’.

const [name, setName] = useState('Harris');
const [age, setAge] = useState(15);dnf install gcc-gfortran python3-devel python2-devel openblas-devel lapack-devel Cython
return (
<div classname="App">
<h1>{name}</h1>
<h1>{age}</h1>
<button onClick={() => {setAge(age+1)}}>Add a Year!</button>
<button onClick={() => {setName('Buddy')}}>Change Name</button>
</div>
)

On the initial render, React creates a linked list with two nodes, one for ‘name’ and one for ‘age’. These nodes don’t contain any information about which variable they belong to; they just hold the value of the state variable.

When the component re-renders, React traverses this linked list, associating the first variable with the first node, the second variable with the second node, and so on. This is why call order is so important: the only way that I know which state I care about for a particular useState call is because the call order needs to be invariant so we are indexing the correct state in the list.

If we want to update a state variable, say ‘age’, the setter function returned by useState knows which node in the linked list to update. This is due to the fact that when the setter is created, it is associated with a particular index in the linked list.

React Hooks and Conditional Calls

The “don’t call hooks inside loops, conditions, or nested functions” rule can seem restrictive, but it ensures data integrity and prevents undesirable consequences.

let died = false;
if (died) {
const [deathDate, setDeathDate] = useState(new Date());
}
const [name, setName] = useState('Harris');
const [age, setAge] = useState(15);
return (
<div classname=”App”>
<h1>{name}</h1>
<h1>{age}</h1>
<button onClick={() => {
if (!died) {
setAge(age+1);
died = true;
}
}}>Add a Year!</button>
<button onClick={() => {setName(‘Buddy’)}}>Change Name</button>
</div>
)

Here, the first useState hook is conditionally called based on the died variable. On the first render, if died is false, React does not associate this useState hook with any state. However, if the condition becomes true in a subsequent render, React attempts to update a non-existing state, leading to inconsistencies and potential errors.

Exploring Alternative Designs

The design choice to associate Hooks with their call order wasn’t arbitrary. It allows the Hook interface to remain clean, simple, and predictable, enabling us to focus more on the logic of our components rather than the complex nuances of state management.

Various alternative designs were considered during the conception of React Hooks. Let’s look at a few of these alternative designs and the challenges associated with them.

First, there’s the idea of having a single state object:

const [state, setState] = useState({
name: “Harris”,
age: 15
})

This approach may simplify the state initialization, but it would lose the advantages of useState providing specific setter functions, which update individual state elements independently and result in less unnecessary re-rendering of components.

const [name, setName] = useState(‘name’);
const [age, setAge] = useState(‘age’);

However, this opens up the potential for name clashes and unexpected behaviour when keys are accidentally reused.

The third alternative considered using Symbols:

const count = Symbol('count');
const MyComponent = () => {
const [count, setCount] = useState(count, 0);
}

However, this approach still isn’t flawless. One really nice thing about hooks is we can compose the built in hooks to make custom hooks. Suppose we created a hook that relied on a symbol like this, then tried to use that hook in multiple other places. We would STILL get name clashes even with the unique symbols. Making it more difficult to compose hooks doesn’t seem like a good tradeoff for not relying on call order here.

Further Reading

Understanding the why behind the design choices of React Hooks can improve your development workflow and the efficiency of your React applications. So keep learning, keep experimenting, and keep developing!

Here are some great readings on the topics above for the curious

Why Do React Hooks Rely On Call Order? — Dan Ambramov

Under the hood of React’s hooks system — Eytan Manor

React Hooks RFC

--

--