#10 How do hooks even work? - Delightful React

#10 How do hooks even work? - Delightful React

React Hooks are ridiculously easy to use but they seem almost magical to be true. How do they even work? It’s important that we figure this out before we start using hooks more and more.

Asset_32.png


*Note: This article is a part of the Delightful React Series, as part of which, I am releasing one chapter every day for 25 days. Please support me in anyway you can! *


When something is that easy in programming, there is always more to it than meets the eye. Hooks are so easy to use, but this easiness is possible because of the rules that come with it.

Let’s try to understand the internals of React in the context of hooks and understand what’s actually going on.

HDHEW.png

React Fiber

React has internal data structure called Fiber. Let me give you a high level overview of what a fiber does. A Fiber is a data structure that does the following.

  • Links a component instance to it’s parents and children as a part of the entire React tree using linked lists
  • Contains information about a component instance’s props and return values
  • Contains information about key prop
  • Contains information about hooks.

Basically, for each element/component in React, there is a fiber object which holds it's information.

React_Fiber_Copy_7.png

All component/element instances have a corresponding fiber object which contains important information about the work it is doing.

The Fiber does quite a few things for a React component instance.

  • When it comes to hooks, the fiber acts as a reservoir to store information about all the hooks in the component.
  • It serves as internal memory for a component and hooks can read/write into that memory.

So, hooks write stuff into the fiber?

Yep. When hooks are used in a component, they are able to maintain information in the fiber across renders. This allows them to maintain an internal state value using the useState hook.

  • Each time a component instance renders, all the hook functions run line by line in the order that they are used in the component.
useState(5)
useState("Bhargav")
useState("Delightful React")

As they run, they read values that are present in the fiber object and use it to render contents.

A closer look at useState

  • Hooks simply maintain important information in fiber and useState does the same thing.
  • The useState hook creates a value in the fiber and simply returns that each time the component renders.
  • It also returns a function to update the value inside fiber as we already saw.
  • Once the value is updated, it triggers a component instance rerender and the next time useState runs, it reads the new value from the fiber.

When a component renders for the very first time and a useState hook runs for the first time in the component, it takes the initial value passed to useState and initialises the state variable using that value and writes it down. It also immediately returns the same value back.

React_Fiber_Copy.png

For every subsequent render, useState doesn’t use the default value argument anymore. It simply returns whatever the current value of the state variable is.

React_Fiber_Copy_10.png

  • When the state variable is updated using the setter function, the value is updated in the memory. Updating state via the setter function triggers a rerender.
const [number, setNumber] = useState(5)

//somewhere
setNumber(6)
  • And now the in the next render, useState runs again and it simply reads the current value of the state variable (recently updated value).

React_Fiber_Copy_11.png

Neat, isn’t it? We are able to create and update state values in a component and the component is also quick to update the changes to the DOM since it rerenders immediately.

Multiple hooks

We know that multiple hooks work in React. Let’s understand how React allocates space for all the hooks in a component.

Well, it simply uses a list to manage hooks

React uses linked lists (an array like structure) to store information about all the hooks a component. Relevant source code can be found here .

During the first render of a component instance, each time a hook function is run, a new item containing information about the hook is added into the list. For useState hook, it stores the initial value of the state variable and this is added as an item into the array.

Screenshot_2021-01-24_at_6.59.15_PM.png

Every time a new useState is encountered in the first render, a new item containing the state variable is added on to the list. So the first useState state variable info is present in the first item in the linked-list, the second state variable in the second item and so on.

When the React component updates because of state/props change, it rerenders again. This time, it goes through each hook.

  • When the first hook is encountered, it reads the first item in the linked list in the component’s fiber.
  • When the second hook is encountered, it reads the second item and so on.

Order of the hooks is important

  • For React to read and write data into the fiber accurately, it relies heavily on the order of hooks and the number of hooks to be the same across rerenders.
  • The order of hooks will stay the same as long as we don’t put hooks in conditional expressions, put hooks in a callback that is called at different times which can skew up the order etc.
  • So, now that we know enough about hooks, let’s look at the rules recommended by the React team.

Hook Rule 1: Hooks can’t be used conditionally.

Let’s take an example here and look closer. Imagine a component that uses hooks like so.

const [number, setNumber] = useState(5)
/* use a hook conditionally */
if(number %2 === 0){
   const [name, setName] = useState("Bhargav")
} 
const [book, setBook] = useState("Delightful React")

return <button onClick={() => setNumber(6)}> Click me </button>

The state variable number starts off at 5 and it can be incremented. Since the value of number state variable is an odd number, number%2 is 1, and hence the second useState is not run the first time. The third state variable “Delightful React” is created.

const [number, setNumber] = useState(5)
/* use a hook conditionally */
if(number %2 === 0){
   const [name, setName] = useState("Bhargav")
} 
const [book, setBook] = useState("Delightful React")

React_Fiber_Copy_5.png

  • When the number value becomes 6, this time all 3 useState calls run and this time we have 3 values to read from an existing list of 2 items.
  • Because there are 3 hooks now and the 2nd hook is now different, it reads and write the value at an incorrect cursor location.

React_Fiber_Copy_6.png

  • This breaks React hooks entirely and React can’t recover from this type of usage at all. Hence, it is very important to not use hooks in if-else conditions.

Hook Rule 2: Don’t call hooks inside other JS functions

Hooks work by using React’s internal Fiber architecture. Without being in a component and without React fiber, the hooks can’t do much any way. So don’t do this.

Hook Rule 3: Hooks must start with the word “use”

  • Since the rules of hooks are important and packages like ESLint can help developers when they use React hooks incorrectly, the keyword “use” is recommended to be used at the start of a hook name.
  • All built-in React hooks use the same naming pattern.
  • The ESLint plugin npmjs.com/package/eslint-plugin-react-hooks helps point out any errors in the usage of hooks in our code editor itself and helps us avoid mistakes.

Great work so far!

We have learnt about React component internals, fiber, and how hooks work, etc. In the next chapter, let’s talk about props and state, data flow in React and also talk about refactor our app.

Thanks and Please support my work

Writing blog posts and making videos is an effort that takes many hours in my day. I do it because I love teaching and make great content. However, I need your support too. Please support my work and follow my social accounts to help me continue to make great content. Here are my social links.

Follow me on Twiter

Subscribe to my channel on Youtube

Thank you!