Learn by implementing React’s useState and useEffect — A simplified overview

Rohit Kashyap
The Startup
Published in
4 min readJun 11, 2020
Photo by Ferenc Almasi on Unsplash

Ever wondered how React implements hooks under the hood? React is an open-source library but navigating through thousands of lines of codes can be quite intimidating.

Although React hooks were first introduced back in 2018, I started using them not too long ago.

Hooks are great! Clean Code, easier to debug, functional components and most importantly: you don’t have to worry about this anymore. All this is great, but there were a few questions that remained unanswered for me.

How does it work?

Since functional components are just functions, How do components preserve their previous state after each re-render?

These questions tempted me to research a bit more about how hooks work under the hood. I started to research the topic and there a lot of excellent explanations out there. But for me, I couldn’t wrap my head around it until I implemented it on my own.

So today, we are going to implement probably the most common hooks you’ll use in a typical application: useState and useEffect.

Before we get started, we need to understand closures a little bit.

Closures:

According to MDN, closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

According to w3Schools, it allows a function to have “private” variables.

These two statements are extremely important to understand how hooks work. Therefore, to understand closures, we’ll go through a simple example

Notice how the inner function still has access to the variable counter defined in the scope of the outer function even after the function has finished executing. The inner function “preserves” the value of the counter and at each function call, it remembers the previous value of the variable.

Also, the variable counter is not available anywhere else except outside outerFunction ( try to access counter anywhere in the code outside the function ). Hence, the w3Schools statement holds as well.

Now let’s get back to where we were, hooks!

Note: This is an oversimplified version of what happens under the hood, React doesn’t use this to power their library.

Let’s start with the overall structure:

Let’s break down the code once before we move forward.

Step 1: We define a global object to keep track of the component’s properties.

Step2: Notice that we are using an index and initialized it to 0. This is used to keep track of the component’s state. This is will become more clear later.

Step 3: A render function which accepts a Component as a parameter and does a couple of things.

Step 4: It first calls the component, stores it’s instance inside the global object so we can use this instance for any future function calls inside the component.

Step 5: Resets the index to 0 and returns the global variable.

Let’s move forward and implement useState.

useState:

What’s happening here?

Step 1: We initialize an empty array that will keep track of the component’s state. If there’s an already existing state, we get that state from the hooks array otherwise we set the state as the initial value (memorizing state).

Let’s look at the function setState:

Step 2: Notice that we copy the value of index into currentIndex.

We are leveraging the power of closures here. Each useState call saves the value of the index inside the variable currentIndex, therefore, with the help of closures, each useState has it’s own preserved value of currentIndex which allows it to know the index at which it’s data is stored inside the hooks array.

Note: Each useState will have its own closure, therefore, it’s own currentIndex before we increment it.

Step 3: We take the new values and overwrite the previous state at the appropriate index and re-render the component.

Step 4: Lastly, we increment the index for further useState calls to store their state inside the hooks array. That’s why we need currentIndex to be preserved inside each closure.

useEffect:

Let’s look at the useEffect function here.

The function accepts a callback function and an array of dependencies similar to the original useEffect hook.

We store the dependencies in the hooks array as a subarray. For any further re-renders, we can check if the value of the dependencies has changed to trigger the callback function.

React uses Object.is() under the hood to compare values. For any further useEffect functions inside our component, we increment the index so it can store its own dependencies at the next index in the hooks array.

Now, let’s write a basic React Component, we will monitor the value of global.hooks and index at each step.

Putting it all together,

Notice that the first useEffect hook runs every time the value of count or word is changed, triggers a re-render and the second useEffect hook runs only one time.

Neat, right?

There are a ton of articles out there explaining and I couldn't have wrapped my head around it without their help.

Read this, this helped me a lot!

Here’s the GitHub repo containing the entire code: https://github.com/rohitpotato/implement-react-hooks

Thanks for reading :)

--

--

Rohit Kashyap
The Startup

Engineer at Falabella India | JavaScript Enthusiast