React useState Hook from Scratch

Knowing how to use React and how it works under the hood are two different things.

The first will make you a good developer, and the second will take you to the next level.

In an effort to understand how things work under the hood, today we will learn how to design useState from scratch.

Let’s begin!

What’s a hook anyway?

So hooks, like components, are nothing but JavaScript functions.

The specialty of the useState hook is that it can track the value of a variable.

Let’s create a basic component for us.

const App = () => {
  const [count, setCount] = useState(0);

  return {
    count,
    setCount
  }
}

Here, you can see we are creating an App component that uses a useState hook.

But the hook is not defined. So let’s define that first.

const useState =(initialState = undefined) => {

  let state = initialState

  const setState = (newStateValue) => {
    state = newStateValue
  }

  return [state , setState ]
}

Here, we are taking a simple function that accepts an initial state.

Also, we are exporting a function that updates the new state.

Now, let’s run the code.

let render = App()
console.log('The value of count is', render.count)

You will see the following

The value of count is 0

Nice!

Let’s Update the state

Let’s try to update our state now. As you know React, you already know that when updating a state, we need to re-render the component. Right?

let render = App()
console.log('The value of count is', render.count)
render.setCount(2)
render = App()
console.log('The updated value of count is', render.count)

Run the code, and you will see the following output.

The value of count is 0

The updated value of count is 0

Mmmm….. Something is not right.

The main thing here is, every everytime we call App(), it re-creates the function. And the state gets reset inside the useState hook.

The solution to that

To retain the value of the state, we can take the state variable out of the function scope and put it in the global scope.

let state;
const useState =(initialState) => {
  state = initialState

  const setState = (newStateValue) => {
    state = newStateValue
  }

  return [state , setState ]
}

Let’s run the code.

The value of count is 0

The updated value of count is 0

So looks like it hasn’t solved out problem yet.

The problem is we are still initializing the state with the initial state. So, we only will update our state if it’s undefined.

let state;
const useState =(initialState) => {
  if(typeof state === "undefined" ) state = initialState

  const setState = (newStateValue) => {
    state = newStateValue
  }

  return [state , setState ]
}

Run the code again, and voila!

The value of count is 0

The updated value of count is 2

That worked! You just created a useState hook by yourself.

Multiple states

Now, let’s take it a step further. What if we want to use multiple state variables in the same component? ( Crazy, right? )

const App = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("initial_name");

  return {
    count,
    setCount,
    name,
    setName
  }
}

And let’s try to update both of our states.

let render = App()
console.log('The value of count is', render.count)
console.log('The value of name is', render.name)

render.setCount(2)
render.setCount("updated_name")

render = App()
console.log('The updated value of count is', render.count)
console.log('The updated value of name is', render.name)

Can you guess the output?

The value of count is 0
The value of name is 0

The updated value of count is updated_name
The updated value of name is updated_name

What’s going on here?

The problem is we are using a single global variable to track multiple values. So, in a way, our previous solution to use global variables caused this issue.

Let’s fix this

So we understand that we need multiple global variables. But how do we do that?

We can create an array that will track the values.

let state = [];
let index = 0
const useState =(initialState) => {
  
  const localIndex = index;
  if(typeof state[localIndex] === "undefined" ) {
    state[localIndex] = initialState
  }
  index++;

  const setState = (newStateValue) => {
    state[localIndex] = newStateValue
  }

  return [state[localIndex] , setState ]
}

You will notice that we are trapping the index value inside a scope using the localIndex const.

It will preserve the pointer position for each of the states.

let render = App()
console.log('The value of count is', render.count)
console.log('The value of name is', render.name)
render.setCount(2)
render.setName("updated_name")

index= 0; // Notice here, we need to reset the index before each render.

render = App()
console.log('The updated value of count is', render.count)
console.log('The updated value of name is', render.name)

If we run the code now, it will execute like the following.

The value of count is 0
The value of name is initial_name
The updated value of count is 2
The updated value of name is updated_name

An interview question for you

Now, can you answer the following? > # Why can’t we put useState inside an if condition?

From the above implementation, you will see that React uses the index to determine which state to update.

So, if you write the following code,

const [count, setCount] = useState(0)
if(some condition){
  const [name, setName] = useState(")
}

Now, the index for the name state will point to the count state. And our application will break.

Conclusion

That’s it for today. Hope you learned something new!


Share this post


Read more articles...

team

Advanced React Optimization Techniques

team

React Interview Questions

Profile Image

Who I am

Hi, I amMohammad Faisal, A full-stack software engineer @Cruise , working remotely from a small but beautiful country named Bangladesh.

I am most experienced inReactJS,NodeJS andAWS

Buy Me a Coffee Widget