Breaking the Rules of React Hooks: Behind the Scenes

Crystal Chang
Jul 24, 2019 · 4 min read
Captain America “So you got detention” meme

We recently started using React Hooks in one of the apps here at Flatiron School. I’ve used React before, but haven’t had the chance to explore Hooks until now so I was pretty excited! I dove into the documentation to get a feel for it and saw this section about the Rules of Hooks, which state that you should:

Only Call Hooks at the Top Level
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls
Only Call Hooks from React Functions
Don’t call Hooks from regular JavaScript functions.

The first rule was particularly interesting to me. It seemed easy enough to follow, but why did the rule exist? What exactly happens if Hooks aren’t called in the exact same order every single time a component is rendered? How convenient that the doc writers linked to an explainer! To summarize the explanation, if Hooks aren’t called in the same order each time a component is rendered, you could have cascading failures where you miss one Hook and none of the subsequent ones are recognized properly. The Hook calls could end up shifted and you’ll get unexpected bugs.

Well, that seems pretty bad. Let’s definitely not allow that to happen. So we now know why the rule shouldn’t be broken, but not why things break. I still really wanted to understand why call order was so important to Hooks. To figure that out I decided to jump into the source code. I started here where the Hooks are defined and eventually followed it over to here where the Dispatcher is. That’s where I saw this note:

Hooks are stored as a linked list on the fiber's memoizedState field. The current hook list is the list that belongs to the current fiber. The work-in-progress hook list is a new list that will be added to the work-in-progress fiber.

Ok, now that’s interesting. The Hooks are stored as a linked list. What’s a linked list? There is actually a ton to linked lists, but in its most basic form a linked list is a data structure that consists of nodes. Each node has its data and also a reference that points to the next node in the list. The order of nodes is entirely dependent on each node’s reference to the next. In Javascript this is can look something like this:

value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null

I won’t be delving into the exact setup of React Hooks as linked lists, but for a detailed breakdown you should check out Linked lists in the wild: React Hooks, which is well worth the read. For our purposes here, we just need to understand that on the first render of a component, a linked list of the Hooks called gets created and on subsequent renders, that list has to be traversable.

This is why we need to make sure Hooks are called in the same order on every render. If one of the Hooks is wrapped in a conditional instead of being called at the top level you could end up with a different number of Hooks on re-render. That means you would end up with a node missing on your linked list of Hooks. If a node is missing, the previous node won’t be referencing the correct thing and the subsequent nodes will be out of order. You could end up with something like this shown in the Rules of Hooks explainer:

useState('Mary')           // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm) // 🔴 This Hook was skipped!
useState('Poppins') // 🔴 2 (but was 3). Fail to read the surname state variable
useEffect(updateTitle) // 🔴 3 (but was 4). Fail to replace the effect

Unsurprisingly, this leads to failures. Also you’ll likely see errors like Rendered fewer hooks than expected or Rendered more hooks than during the previous render. While at first it struck me as odd that React Hooks are dependent on call order, looking at the code, it makes a lot of sense. With the linked list set up, disappearing or disordered nodes would cause cascading failures and nobody wants that.

Thanks for reading! Want to work on a mission-driven team that loves React and deep dives into source code? We’re hiring!

To learn more about Flatiron School, visit the website, follow us on Facebook and Twitter, and visit us at upcoming events near you.

Flatiron School is a proud member of the WeWork family. Check out our sister technology blogs WeWork Technology and Making Meetup.

Flatiron Labs

We're the technology team at The Flatiron School (a WeWork…