Skip to content

Aquent | DEV6

Demystify React Hooks

Written by: Peter Wang

With Leonard’s Introduction to React Hooks paving the way, it is time we take a deep dive into how these seemingly magical “hooks” actually work by reimplementing React Hooks in our own way.

By the end of this article, we’ll have a better and deeper understanding of how React hooks work, and hopefully this article, to some extent, is going to inspire you to grasp a new concept by implementing it yourself.

Understand the example:

There is a simple Counter example on the Introducing Hooks page of the react docs. Let’s first look at this example and try to understand what it is doing there.

import React, {useState} from "react";
import ReactDOM from "react-dom";
 
function Example() {
 
  // Declare a new state variable, which we'll call "count"
 
  const [count, setCount] = useState(0);

  return (
 
    <div>
 
      <p>You clicked {count} times</p>
 
      <button onClick={() => setCount(count + 1)}>
 
        Click me
 
      </button>
 
    </div>
 
  );
 
}

ReactDOM.render(<Example />, document.getElementById("root"));

Here we have this useState hook that “magically” gives us the state of our application, which we name count, and the state update function, which we name setCount. We use this count variable to display how many times button is clicked, and we call setCount in a button’s onClick event handler to update the state, which is the count variable here.

Now let’s go through a sequence of events that is happening when a button is being clicked.

  1. Button is clicked first time –> setCount(1)
  2. setCount(1) –> component re-renders
  3. component re-renders –> useState(0) is called again
  4. count becomes 1 // magic?

Let’s take a closer look at event 3 and 4 here. When useState(0) is called again, somehow the  useState hook remembers the state and changes it to the new value set by the setCount call, which is 1 in this case. What’s really happening in that useState hook? Let’s find out by trying to write our own version of it.

Rewrite useState hook:

Let’s first create the skeleton of the hook.

function useState(defaultValue) {
 
  const setValue = (newValue) => {
 
    // something magical
 
  }
 
  return [defaultValue, setValue]
 
}

According to React hooks signature, it’s a function that takes a default value and returns an array of two items, state and state updater function.

Now let’s think about what should happen in that setValue state updater function. Our first instinct is to just assign the newValue to defaultValue like so:

const setValue = (newValue) => {
 
    defaultValue = newValue
 
  }

This immediately comes with an issue: the return value of useState is exposed on the first call, after first call, no matter how many times setValue is called, the state will NOT get changed. This means that we need a way to keep track of the return value of every useState call. Let’s create an empty array called states to store all the state value and updater function and a number called callId to keep track of the number of useState calls.

const states = []
 
let calls = -1
 
function useState(defaultValue) {
 
  const callId = ++calls
 
  const setValue = (newValue) => {
 
    states[callId][0] = newValue
 
  }
 
  const result = [defaultValue, setValue]
 
  states[callId] = result
 
  return result
 
}

Once we have states and calls, we can easily find a reference to the defaultValue in the result inside setValuefunction and set it to newValue. However, we forget about one very important thing here, that is, re-render! In React, whenever a state is changed, components re-render. React hooks take care of re-rendering, so our own useState hook should too. So, let’s make a render function to call whenever state changes.

function render() {
 
  ReactDOM.render(<Example />, document.getElementById('root'));
 
}

And we can call it inside the setValue function because it is when state is changed.

const setValue = newValue => {
 
  states[callId][0] = newValue;
 
  render();
 
};

Looking good so far. But it still does not work. Why? Because we are not exposing the newly updated state when setValue is called. Also, we are incrementing calls every time we re-render, which causes the states array to grow larger and larger while we only have one state. How do we solve these two issues? Second issue seems easy to fix. We only need to reset calls to -1 in render function.

function render() {
 
  calls = -1;
 
  ReactDOM.render(<Example />, document.getElementById('root'));
 
}

Now, for the first issue, the process is that when useState is called first time, we store the state into states array and callId is 0. Now when we click the button, which calls setValue. Inside setValue, we find the state by callId – it is still 0 at that moment – and update the state in states array. What’s really changed is the states array. So we can just return the state for that particular call.

const callId = ++calls;
 
if (states[callId]) {
 
  return states[callId];
 
}

What we are doing here is that if we already have state store at this call, just return it.

That’s it. This is all that useState is doing under the hood.

Recap:

This is the full implementation of our own useState hook:

const states = [];
 
let calls = -1;
 
function useState(defaultValue) {
 
  const callId = ++calls;
 
  if (states[callId]) {
 
    return states[callId];
 
  }
 
  
 
  const setValue = newValue => {
 
    states[callId][0] = newValue;
 
    render();
 
  };
 
  const result = [defaultValue, setValue];
 
  states[callId] = result;
 
  return result;
 
}
 
function render() {
 
  calls = -1;
 
  ReactDOM.render(<Example />, document.getElementById('root'));
 
}
 
render();
  1. keep track of all the state in an array
  2. keep track of the calls of every state update
  3. find correct state by callId and update the state
  4. return state from states array if it already exists

Conclusion:

This is obviously extremely simplified and experimental. There are many things we have not taken into consideration when writing this implementation. However, writing your own implementation really helps you understand the mental model of a new concept. Hope this article offers a new perspective of how you can learn and understand a new concept better and faster.

If you’d like to learn more about React hooks and learn the actual implementation of React hooks, this article is a good start.

Want to learn more React?

Sign up for our React Fundamentals course

View Course Details