Creating a useState hook from scratch
Published: 2023-06-22 12:00:00

In this article, we’ll create the most barebones version of the useState hook. If you want to understand how React hooks work in depth, how they use closures, and why can’t you nest hooks in `if` blocks - read ahead!

First of all, you probably know that React doesn’t need JSX, and in the end, it transforms everything to `React.createElement(component, props, ...children)` . You can read more about it in the official docs [https://reactjs.org/docs/react-without-jsx.html](https://reactjs.org/docs/react-without-jsx.html)

With this in mind, to not overcomplicate ourselves, let’s create just a .ts file called index.ts. Then, we can add our simple custom component without any JSX.

```jsx
function App() {
  const [count, setCount] = useState(0);

  return {
    count: `Count is ${count}`,
    setCount
  };
}
```

To emulate rendering and re-rendering, we’ll do the same thing that React does  - just call the function. Then we’ll get our count and log it to see what’s there.

```jsx
function App() {
  const [count, setCount] = useState(0);

  return {
    count: `Count is ${count}`,
    setCount
  };
}

const Rendered = App();
console.log(Rendered.count);
```

Oh, right, `useState` is not defined… Let’s create it!

We need our function to accept `initialState` and return an array of two items - state and a function to modify it.

```jsx
function useState(initialState?: any) {
  let state = initialState ?? undefined;

  return [state, (newState: any) => (state = newState)];
}
```

Let’s run our code again

![console output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8p2rrcyqn3zk477s8r19.png)

We’re getting somewhere :). But let’s try to change our state

```jsx
let Rendered = App();
console.log(Rendered.count);
Rendered.setCount(10);
Rendered = App() //re-render after state change
console.log(Rendered.count);
```

![Console output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mh0a88zvg8az6k3qpd55.png)

Same thing. That’s because our useState function gets re-created on every re-render 

We can use closures to our advantage here and lift the state up to be a global variable not to get re-created on every re-render.

```jsx
let state: any;

function useState(initialState?: any) {
  /* We want to check if state is undefined, i.e. it's the first render
     before assigning initialState to it.
  */
  if(typeof state === 'undefined') state = initialState;

  return [state, (newState: any) => (state = newState)];
}
```

![console output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/436038ojko177zng24zp.png)

Much better, but how do we go about creating two states? Three states?

```jsx
function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("John");

  return {
    count: `Count is ${count}`,
    setCount,
    name: `Name is ${name}`,
    setName
  };
}

let Rendered = App();
console.log(Rendered.count);
console.log(Rendered.name);
Rendered.setCount(10);
Rendered.setName("Mark");
Rendered = App(); //re-render after state change
console.log(Rendered.count);
console.log(Rendered.name);
```

![console ouput](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1htav7y73nt8h8ded0dt.png)

Since we only have one global state variable, our two `useStates` have to share it, which leads to incorrect behavior.

The way React implements it - is by having a state array and a pointer to know which useState we are currently on. After the useState function is fired, it increments this pointer so that the next useState function will work with its state, and so on. After a re-render, since our App() and all the useState functions are called again, we need to set this pointer to 0. Can you already guess why nesting a useState in an if statement is bad?

```jsx
let state: any[] = [];
let index = 0;

function useState(initialState?: any) {
  // Freeze the index, to make the 'setters'(setCount/setName) work
  const localIndex = index;
  if (typeof state[localIndex] === 'undefined') state[localIndex] = initialState;

  index++;
  return [state[localIndex], (newState: any) => (state[localIndex] = newState)];
}

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

  return {
    count: `Count is ${count}`,
    setCount,
    name: `Name is ${name}`,
    setName
  };
}

let Rendered = App();
console.log(Rendered.count);
console.log(Rendered.name);
Rendered.setCount(10);
Rendered.setName("Mark");
index = 0; //Set the index to 0
Rendered = App(); //re-render after state change
console.log(Rendered.count);
console.log(Rendered.name);
```

One more trick we are doing here is “freezing” the index. Otherwise, when we get to the “name” useState, it’ll increment the index to 1, and setCount will end up changing the name instead of count. Because name has an index of 1 and count has an index of 0

Let’s see where it got us!

![console output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3tbxb416401vzfq610sa.png)

Works perfectly!

## Conclusion

Of course, having built this we are still far of from becoming a React maintainer at Facebook :) But the best way to learn something is by doing it. So here are a couple of ways you can improve this on your own

- Instead of manually setting index to 0 before calling our function every time, encapsulate this into a render method which’d do both.
- Store your custom React in an IIFE [](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)instead of globally in our index.ts file, just like React does it [https://developer.mozilla.org/en-US/docs/Glossary/IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)
- Implement useEffect
- Use your imagination and implement more stuff!

This article was inspired by two great talks:

[https://www.youtube.com/watch?v=KJP1E-Y-xyo](https://www.youtube.com/watch?v=KJP1E-Y-xyo)

[https://www.youtube.com/watch?v=f2mMOiCSj5c](https://www.youtube.com/watch?v=f2mMOiCSj5c)

Thank you for reading!

---

At MiKi(https://miki.digital/) we help businesses make stunning websites with a blazing-fast and cost-efficient cloud backend.
Interested?
Feel free to give us a call at +447588739366, book a meeting at https://calendly.com/miki-digital/30-minute-consultation or feel out the contact form at our website https://www.miki.digital/contact