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!