If you prefer video over text. Here’s a video i made a few weeks back.
The useState hook is probably the most used hook in React. For those who don’t remember, here’s a quick refresher.
const [count,setCount] = useState(0)
| | |
value mutation initial value
The useState hook is a function that returns, a value and a function to mutate that value. An initial value can be passed as parameter to the useState function.
So if you dumb it down, useState is basically a “stateful” function. Stateful here means, the variable value is persisted across multiple re-runs of the function.
With that knowledge, let’s start our journey of writing useState hook for scratch. We will first try to build a naive solution and then build on that.
Super Naive.
let count = 0;
function addCount() {
count = count + 1;
}
addCount();
console.log(count) //1
addCount();
console.log(count) //2
Like i said, pretty naive solution. Declaring state in global scope is pretty bad. Any other piece of code can change it and break our component “state”. Also we haven’t figured out how to set the initial state
We can do better.
const React = (function(){
function useState(initVal) {
let count = initVal;
function setCount() {
count = count + 1;
}
return [count, setCount]
}
return {useState}
})()
let [num, setNum] = React.useState(0);
console.log(num) //0
setNum()
console.log(num) //0 ????
This might seem overwhelming so let me break it down.
- An IIFE returns an object which holds the
useStatemethod. - The
useStatemethod accepts an initial value and returns an array with 2 itemscountsetCount→ a method to mutatecountie. increments the count by 1.
Dry run
On calling the useState hook, the count variable is set to initial value ie.0 and that can be verified by the output of the 1st console.log statement.
Then to mutate the count value we call setNum and ……. it doesn’t work! Shoot!
The problem is, num isn’t reflecting the newly set value.
It works! (almost) 🥳.
const React = (function(){
let _count;
function useState(initVal) {
_count = _count || initVal;
const count = () => _count
function setCount() {
_count = _count + 1;
}
return [count, setCount]
}
return {useState}
})()
let [num, setNum] = React.useState(0);
console.log(num()) //0
setNum()
console.log(num()) //1
console.log("🥳") // 🥳
What changed?
- Renamed
countto_count - Moved
_countinto the closure of useState function. This is important! _countvalue is set toinitValif_countisn’t set.- Created a function
countwhich returns_count
But wait, now count isn’t a variable anymore. It’s a function! This isn’t how useState works!
Let’s Re(a)ctify this!
const React = (function(){
let count;
function useState(initVal) {
count = count || initVal;
function setCount(newVal) {
count = newVal;
}
return [count, setCount];
}
function render(component) {
const a = component();
a.render();
return a;
}
return {useState, render}
})()
function wrapper() {
const [num, setNum] = React.useState(0);
return {
render: () => {console.log(`Renderd with num: ${num}`)},
click: () => {setNum(num + 1)}
}
}
let component = React.render(wrapper); // Rendered with num: 0
component.click(); // changes component state
component = React.render(wrapper); // Rendered with num: 1
component.click()
component = React.render(wrapper); // Rendered with num: 2
component.click()
component = React.render(wrapper) // Rendered with num:
What changed?
- Added a
rendermethod in the object returned by IIFE. - Added a component
wrapperwhich uses theuseStatehook. The component returns an object with 2 methods →renderwhich logs a string to the console since we aren’t interacting with DOM here.clicka function which mutates the state. Typically this would be a event handler.
Dry run
- The component is rendered with initial value ie.
0. - The
clickcall updates the value ofcountinternally. Since the state has been mutated, we re-render the component. - This time during render, since
countvariable is set, the value ofnumis set to1and the component is rendered with that value.
Notice how closures help in persisting the value of count across multiple re-runs of the useState function.
And that’s it! That’s how you write a useState hook from scratch.
If you found the article helpful, share it with other folks. This article is based on a talk Getting closure on react hooks by swyx. Do check it out!
You can follow me on twitter. I recently tweeted about How dates are broken in JavaScript.
I also make videos on YouTube! These are some of my most viewed ones