DEV Community

Mohammad Faisal
Mohammad Faisal

Posted on • Edited on • Originally published at mdfaisal.com

React useState hook from Scratch

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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 ]
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

You will see the following

The value of count is 0
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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

The value of count is 0

The updated value of count is 0
Enter fullscreen mode Exit fullscreen mode

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 ]
}
Enter fullscreen mode Exit fullscreen mode

Let’s run the code.

The value of count is 0

The updated value of count is 0
Enter fullscreen mode Exit fullscreen mode

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 ]
}
Enter fullscreen mode Exit fullscreen mode

Run the code again, and voila!

The value of count is 0

The updated value of count is 2
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ]
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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(")
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)