Some interesting points about React's useState hook
Tags: react, javascript
Published: 2021-08-19 15:23:44

React's `useState` hook is used to manage the state of your application and is seen quite often.

Today in this article I would like share some facts about this hook which you might not know and which might increase your understanding of this hook works.

### 🚀 Setting state with a value similar to the current state will not cause a re-render.

Suppose you have a state `foo` that's currently set to `"Foo"` and you call `setFoo` again with `"Foo"`, it will not cause a re-render. Check the example below:

```jsx
const App = () => {
 const [foo, setFoo] = React.useState("Foo");

 React.useEffect(() => {
  console.log("Rendered");
 });

 return <button onClick={() => setFoo("Foo")}>Click</button>;
};

const rootEl = document.getElementById("root");
ReactDOM.render(<App />, rootEl);
```
<hr>

### 🚀 Updating state by passing in a callback
To set state we can either pass the new state value directly or we can pass in a function that takes as an argument the current state and returns the new state.

I prefer the second approach when my new state depends on the current state, for ex: `setCount(currCount => currCount + 1)` instead of `setCount(count + 1)`.

```jsx
const Counter = () => {
 const [count, setCount] = React.useState(0);

 const handleClick = () => {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
 };

 return (
  <>
   <p>Current Count: {count}</p>
   <button onClick={handleClick}>Add 3</button>
  </>
 );
};

const rootEl = document.getElementById("root");
ReactDOM.render(<Counter />, rootEl);
```

In example above although we call `setCount` thrice but the `count` only gets updated by one and this because React batches al these three calls.

So, suppose `count` is currently `0` and you clicked the button. Now, what React sees is the following:
>`setCount(0 + 1)`
`setCount(0 + 1)`
`setCount(0 + 1)`

React executes the first `setCount` call after which `count` becomes `1` and now the other two `setCount` calls are essentially updating the state to what it already is and we saw in the previous section that React is smart enough to simply ignore this.

To fix this we need to update the `count` state using the callback approach. So, we change the `handleChange` function to the following:

```jsx
const handleClick = () => {
 setCount((currCount) => currCount + 1);
 setCount((currCount) => currCount + 1);
 setCount((currCount) => currCount + 1);
};
```

React will again batch all these calls, which you can confirm by adding the following in your code (_"Rendered" should be logged only once_).
```jsx
React.useEffect(() => {
 console.log("Rendered!");
});
```

So, when React encounters the first `setCount` call the `currCount` is `0`, so it is updated to `0 + 1`. 

For the second `setCount` call the `currCount` becomes `1`, so it is updated to `1 + 1` and similar for the third call.
<hr>

### 🚀 Lazy Initializers
Suppose you've an `input` and whatever your users type in the input gets stored in the `localStorage` so that if the page reloads your users can continue from where they left.

The example below does exactly the same thing. So, to initialize the `firstName` state we call the `getDataFromLS` function which retrieves the data from `localStorage` and whatever this function returns becomes the initial value of the `firstName` state.

❗ **NOTE:** Don't be confused that we have passed a function to `useState`, we haven't. We've called it there itself, which means we've just passed the value that the function returns.

```jsx
const getDataFromLS = (key) => {
 console.log(`Retrieving ${key} from Local Storage`);
 const value = window.localStorage.getItem(key) || "";
 return value;
};

const App = () => {
 const [firstName, setFirstName] = React.useState(
  getDataFromLS("firstName")
 );

 return (
  <>
   {firstName && <h1>Hello {firstName}</h1>}
   <form>
    <div>
     <label htmlFor="name">Your First Name: </label>
     <input
      id="name"
      value={firstName}
      onChange={({ target }) => {
       localStorage.setItem("firstName", target.value);
       setFirstName(target.value);
      }}
     />
    </div>
   </form>
  </>
 );
};

const rootEl = document.getElementById("root");
ReactDOM.render(<App />, rootEl);
```

The initial value passed to `useState` is only used for the first time. On subsequent calls to `useState` React figures out that this is not the first time that this hook is being called and so it doesn't set the state variable to its initial value but instead sets it to its current value.

But, if you open the devtools and see the logs you would see that
for every re-render the `getDataFromLS` function is called (_confirmed by the logs_).

So, even if React doesn't care what `getDataFromLS` function returns on subsequent calls it still calls that function. 

This is quite okay for this example but this could impact performance if you're doing some complex operations to get that initial value.

Let's add another `input` field but this time we will set the initial value differently.

```js
const getDataFromLS = (key) => {
 console.log(`Retrieving ${key} from Local Storage`);
 const value = window.localStorage.getItem(key) || "";
 return value;
};

const App = () => {
 const [firstName, setFirstName] = React.useState(
  getDataFromLS("firstName")
 );

 const [lastName, setLastName] = React.useState(() =>
  getDataFromLS("lastName")
 );

 const handleChange = () => {};

 return (
  <>
   {(firstName || lastName) && (
    <h1>
     Hello {firstName} {lastName}
    </h1>
   )}
   <form>
    <div>
     <label htmlFor="name">Your First Name: </label>
     <input
      id="name"
      value={firstName}
      onChange={({ target }) => {
       localStorage.setItem("firstName", target.value);
       setFirstName(target.value);
      }}
     />
    </div>

    <div>
     <label htmlFor="name">Your Last Name: </label>
     <input
      id="name"
      value={lastName}
      onChange={({ target }) => {
       localStorage.setItem("lastName", target.value);
       setLastName(target.value);
      }}
     />
    </div>
   </form>
  </>
 );
};

const rootEl = document.getElementById("root");
ReactDOM.render(<App />, rootEl);
```

So, this time instead of calling the function there itself, we passed a function to `useState` which React will call (_not us_) and whatever this function returns is set as the initial state. This is referred to as **"Lazy Initialization"**.

❗ **NOTE**: React calls this function synchronously so this function cannot be asynchronous.

Now, you would only see the "Retrieving lastName from Local Storage" log once (_unless the component gets unmounted and mounted again_) but you would see the "Retrieving firstName from Local Storage" every time the component re-renders.
<hr>
That's It! 🤘

Hope, you found this useful and learned something new. Let me know your thoughts in the comments.