Why do we need useState in functional React

If you have used React before, you might know about React's functional components. You might have also came across useState. So what exactly is it and why do we need it?

Let's take a look at the following example.

import React, { useState } from "react";
import ReactDOM from "react-dom";
const App = () => {
let counter = 0;
const increment = () => {
counter += 1;
console.log(counter);
};
return (
<div>
<p>nonUseStateCounter: {counter}</p>
<button onClick={increment}>Increment</button>
</div>
);
};

Now if we click the increment button twice, you can see that the counter is updated correctly via the console. However, the UI is never updated with the new counter value. Why is that?

first pic

It is because nothing has triggered the component to re-render. How do we know this?

Because if the component re-renders, it will re-run the App function, causing counter to be reset back to 0. You can also test this by console.log anything before the return.

const App = () => {
console.log("render");
let counter = 0;
const increment = () => {
counter += 1;
console.log(counter);
};
return (
<div>
<p>nonUseStateCounter: {counter}</p>
<button onClick={increment}>Increment</button>
</div>
);
};

second pic

This is why we need to use useState because useState can trigger re-render when the associated state changes. Now let's combine both useState and non-useState to demonstrate the difference.

const App = () => {
console.log("render");
let [useStateCounter, setUseStateCounter] = useState(0);
let counter = 0;
const increment = () => {
counter += 1;
setUseStateCounter((prevCounter) => {
const newCounter = prevCounter + 1;
console.log(`useStateCounter: ${newCounter}`);
return newCounter;
});
console.log(`counter: ${counter}`);
};
return (
<div>
<p>useStateCounter: {useStateCounter}</p>
<p>nonUseStateCounter: {counter}</p>
<button onClick={increment}>Increment</button>
</div>
);
};

third pic

As you can see, useStateCounter was updated correctly and is responsible for causing the App component to re-render. We can tell the re-render is happening because render is outputted every time we click the button. Counter also stays as 1 because the re-render is causing the counter to be re-declared and re-initialized to 0.

This is precisely why you should use useState for persist states across component re-renders.