Wait... how does React.useState work?
Tags: tutorial, react, beginners
Published: 2020-08-20 00:54:55

So React hooks have been released for a while now and they are great! I have used them in production code and it makes everything look nicer. As I continued to use hooks, I started to wonder how all this magic works.

Apparently I was not the only one because there was a Boston React meetup on this topic. Big Thank you to Ryan Florence and Michael Jackson (Not the Moonwalking legend) for giving such a great talk around the subject. Continue watching and you will learn more about `useEffect` and how that works!
{% youtube 1jWS7cCuUXw %}

## How does it work?
You create a functional component and throw some React hook at it that tracks state, can also update it, and it just works.

Many of us have seen some variation of this example before:

## One `useState`
```javascript
import React from "react";

const App = () => {
  const [count, setCount] = React.useState(1);

  return (
    <div className="App">
      <h1>The infamous counter example</h1>
      <button onClick={() => setCount(count - 1)}>-</button>
      <span style={{ margin: "0 16px" }}>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
};

export default App;
```
👏 👏 👏 It works!
![Counter figure 1](https://dev-to-uploads.s3.amazonaws.com/i/vkw3oocepnkrn6btecce.gif)

Ok great, but how does it do that magic? Look at the `React.useState` line. It's so easy to read that I never questioned it. I have a destructed array that extracts the `count` value and some function called `setCount` and it will initialize count with the default value that I passed into `useState`. What happens when I add another `React.useState` to the picture?

## Two `useState`, ha-ha-ha
Count Dracula anyone?

```javascript
const App = () => {
  const [count, setCount] = React.useState(1);
  const [message, setMessage] = React.useState("");

  const adder = () => {
    if (count < 10) {
      setCount(count + 1);
      setMessage(null);
    } else {
      setMessage("You can't go higher than 10");
    }
  }

  const subtracter = () => {
    if (count > 1) {
      setCount(count - 1);
      setMessage(null);
    } else {
      setMessage("You can't go lower than 1, you crazy");
    }
  }

  return (
    <div className="App">
      <h1>The infamous counter example</h1>
      <button onClick={subtracter}>-</button>
      <span style={{ margin: "0 16px" }}>{count}</span>
      <button onClick={adder}>+</button>
      <p>{message}</p>
    </div>
  );
};
```

Now we are showing a message whenever a user is trying to go outside the bounds of 1 - 10
![Counter Figure 2](https://dev-to-uploads.s3.amazonaws.com/i/o2ylcqiqnng6rc4w1b00.gif)

In our component, we have two destructured arrays that are using the same `React.useState` hook with different default values. Whoa, now we are getting into the magic of it all.

Alright so lets delete our `React` from `React.useState` we should get a referenceError saying, "useState is not defined"
![Reference error because React is gone](https://dev-to-uploads.s3.amazonaws.com/i/2cx77uybiflvv79nwhys.png)

Let's implement our own `useState` function.

## Reverse engineering the `useState` function
A `useState` function has a value and a function that will set that value

Something like this:
```javascript
const useState = (value) => {

  const state = [value, setValue]
  return state
}
```
We are still getting referenceErrors because we haven't defined `setValue`. We know that setValue is a function because of how we use it in `useState`
Our count `useState`: `const [count, setCount] = React.useState(1);`

Calling `setCount`: `setCount(count + 1);`


Creating the `setValue` function results in no more error but the `-` and `+` buttons don't work.

```javascript
const useState = (value) => {
  const setValue = () => {
    // What do we do in here?
  }

  const state = [value, setValue]
  return state
}
```
If we try and change the default value in `useState` it will update `count` 👍🏽. At least something is working 😂.
![Counter figure 3](https://dev-to-uploads.s3.amazonaws.com/i/ql0xqbggr1b4z4hga9ia.gif)

### Moving on to figuring out what the hell `setValue` does.

When we look at `setCount` it's doing some sort of value reassignment and then it causes React to rerender. So that is what we are going to do next.

```javascript
const setValue = () => {
  // What do we do in here?
  // Do some assigning
  // Rerender React
}
```

We will pass in a new value argument to our `setValue` function. 

```javascript
const setValue = (newValue) => {
  // What do we do in here?
  // Do some assigning
  // Rerender React
}
```

But what do we do with `newValue` within the `setValue` function?

```javascript
const setValue = (newValue) => {
  // Do some assigning
  value = newValue // Does this work?
  // Rerender React
}
```

`value = newValue` makes sense but that does not update the value of the counter. Why? When I `console.log` within `setValue` and outside of `setValue` this is what we see.

![Counter does not update when + is clicked](https://dev-to-uploads.s3.amazonaws.com/i/nnzm7euilj4or1hqirga.gif)

So After I refresh the page. The count is initialized to 1 and the message is initialized to null, great start. I click the `+` button and we see the count value increase to 2, but it does not update count on the screen. 🤔 Maybe I need to manually re-render the browser to update the count?

### Implement a janky way to manually re-render the browser

```javascript
const useState = (value) => {
  const setValue = (newValue) => {
    value = newValue;
    manualRerender();
  };
  const state = [value, setValue];
  return state;
};
.
.
.
const manualRerender = () => {
  const rootElement = document.getElementById("root");
  ReactDOM.render(<App />, rootElement);
};

manualRerender();
```
This still doesn't update count in the browser. What the heck? 

I was stuck on this for a little while and now I know why. Let's `console.log` state right after we create it.

```javascript
const state = [value, setValue];
console.log(state)
```
Our call to `useState` causes the first render, and we get:
`[1, setValue()]`

And on our second call to `useState` we render:
`[null, setValue()]`

resulting in:
![first render](https://dev-to-uploads.s3.amazonaws.com/i/53tigt09sbs08tjvs1zo.png)

To help visualize this a bit better, let's add a render tracker to count how many times we render the screen.

```javascript
let render = -1

const useState = (value) => {
  const setValue = (newValue) => {
    value = newValue;
    manualRerender();
  };
  const state = [value, setValue];
  console.log(++render)
  console.log(state)
  return state;
};
```
![Track renders when useState is called](https://dev-to-uploads.s3.amazonaws.com/i/bns9gvs2lcvjkh68mnrn.gif)

How does our `setValue` function know which value to update? It doesn't, therefore we need a way to track it. You can use an array or an object to do this. I choose the red pill of objects.

Outside of `useState` function, we are going to create an object called `states`

```
const states = {}
```

Within the `useState` function initialize the `states` object. Let's use the bracket notation to assign the key/value pair.

`states[++render] = state`

I am also going to create another variable called `id` that will store the render value so we can take out the `++render` within the brackets.

You should have something that looks like this:
```javascript
let render = -1;
const states = {};

const useState = (value) => {
  const id = ++render;

  const setValue = (newValue) => {
    value = newValue;
    manualRerender();
  };
  const state = [value, setValue];
  states[id] = state;
  console.log(states);
  return state;
};
```

What does our `states` object look like? 

```
states = {
  0: [1, setValue],
  1: [null, setValue]
}
```

So now when we click the add and subtract buttons we get... nothing again. Oh right because `value = newValue` still isn't doing anything.

But there is something that is happening. If you look at the console you will see that every time we click on one of the buttons it will keep adding the same arrays to our `states` object but `count` isn't incrementing and message is still null.

So `setValue` needs to go look for `value`, then assign the `newValue` to `value`.

```javascript
const setValue = (newValue) => {
  states[id][0] = newValue;
  manualRerender();
};
```

Then we want to make sure we are only updating keys: 0 and 1 since those will be our two `useState` locations.

So head down to the `manualRerender` function and add a call to `render` and reassign it to -1

```javascript
const manualRerender = () => {
  render = -1;
  const rootElement = document.getElementById("root");
  ReactDOM.render(<App />, rootElement);
};
```

We do this because every time we call setValue it will call the `manualRerender` function setting `render` back to -1

Lastly, we will add a check to see if the object exists. If it does then we will just return the object.

```
if (states[id]) return states[id];
```

Now we work again!
![Counter working](https://dev-to-uploads.s3.amazonaws.com/i/te6m7hnuoou0530ep42g.gif)

Phew. That was a lot to process and this is just a very simplistic approach to `useState`. There is a ton more that happens behind the scenes, but at least we have a rough idea of how it works and we demystified it a bit.

Take a look at all the code and try and make a mental model of how it all works.

{% codesandbox react-hooks-deconstructed-mj5tk %}

Hope this helps 😊