useState in React: Why Your Variable Stays the Same (and How to Fix It)


Beginner's Guide to useState
I wonβt lie, it took me 4 days to thoroughly understand the useState
hook. It's one of the first and most fundamental concepts in React, but it can be tricky. If you've ever felt that frustration, this post is for you. We're going to dive deep, starting from the basics.
Letβs go to our good old GOAT π friend: plain JavaScript.
The Plain JavaScript Approach
Imagine we have a simple component. We want to display a value, let's call it state
.
JavaScript
export default function App() {
let state = "No"
state = "Damn No!" // We re-assign it immediately
return (
<main>
<h1 className="title">Do you know useState()?</h1>
<button className="value">{state}</button>
</main>
)
}
Here, I declared a state
variable using let
. Because the code runs from top to bottom, the final value assigned is "Damn No!", and that's what shows up on the screen.
This isn't rocket science π. I agree. But now, letβs do some magic πͺ. What if we want to change the variable's value only when the user clicks the button?
In plain JavaScript, you'd add an onClick
event listener and call a function. Let's try that.
JavaScript
export default function App() {
let state = "No";
function handleClick() {
state = "Damn No!";
console.log("Function called! New state:", state); // Checking our work
}
return (
<main>
<h1 className="title">Do you know useState()?</h1>
<button className="value" onClick={handleClick}>
{state}
</button>
</main>
);
}
When you run this and click the button, something surprising happens. If you check your developer console, you'll see "Function called! New state: Damn No!". The variable is changing. But the text on the button in the UI remains "No". π§
This brings us to our first golden rule.
Golden Rule #1 ππ½: Simply changing the value of a local variable will not make React "react." It wonβt trigger React to re-run the component and update what you see on the screen (the DOM).
We have to use the method React gives us. React is the boss here!
Enter useState
Here comes the useState()
function (Iβm keeping it simple, as the term βhookβ scared π° me at first). Since React provides it, we need to import it.
JavaScript
import { useState } from "react";
Side Note: What's with the curly braces
{}
? This is called a "named import." It means we are importing a specific piece of code that the"react"
library has explicitly exported with that name. If you were to peek inside React's code (e.g., in/node_modules/react/
), you'd find manyexport
statements.useState
is just one of them. We use curly braces to grab it by name.
Now, let's see what this useState
function gives us.
JavaScript
import { useState } from "react";
export default function App() {
const result = useState();
console.log(result); // Let's inspect this
return (
<main>
<h1 className="title">Do you know useState()?</h1>
<button className="value">Click Me</button>
</main>
);
}
If you check the console, youβll see it logs an array with two items: [undefined, Ζ()]
. The first item is our state value (it's undefined
because we didn't give it a starting value), and the second is a function.
This leads to our second rule.
Golden Rule #2 ππ½:
useState()
always returns an array containing exactly two things:
The current state value.
A function to update that state value.
Let's give our state an initial value. The useState
function takes one argument: the initialState
.
JavaScript
const result = useState("Yes");
console.log(result); // Will now log: ["Yes", Ζ()]
Great! We know how to set an initial state. Now, you might be tempted to do this to display it:
JavaScript
import { useState } from "react";
export default function App() {
let result = useState("Yes");
function handleClick() {
result[0] = "Heck Yes!"; // Let's try to change it directly
console.log("Clicked! New result:", result);
}
return (
<main>
<h1 className="title">Is state important to know?</h1>
<button className="value" onClick={handleClick}>
{result[0]}
</button>
</main>
);
}
You click the button, and... nothing changes on the screen. Why? You forgot Golden Rule #1! We are still trying to change a value directly without telling React it needs to update the UI.
I know you might be pulling your hair out, but wait! Remember that second item useState
gives us? The function? That's the key.
Using State Correctly: Destructuring and the Setter Function
The standard way to work with the useState
array is to "destructure" it into two separate variables. It's cleaner and easier to read.
JavaScript
// Instead of: const result = useState("Yes");
// We do this:
const [result, setResult] = useState("Yes");
result
: This is now our state variable. Its initial value is "Yes".setResult
: This is the special function we must use to updateresult
.
The convention is to name the function set
followed by the variable name (e.g., [name, setName]
, [isLoggedIn, setIsLoggedIn]
), but you could technically call it anything. Sticking to convention makes you a better, more readable developer.
Now, let's put setResult
to work. But be careful! A common mistake is to call the setter function directly in your JSX:
JavaScript
// π¨ DON'T DO THIS π¨
return (
<main>
<h1 className="title">Is state important to know?</h1>
{/* This causes an infinite loop! */}
<button className="value">{setResult("Heck Yes!")}</button>
</main>
);
This is a classic "too much React" error. Calling setResult
tells React to re-render the component. But since setResult
is being called during the render, it immediately triggers another re-render, which calls setResult
again, and so on, creating an infinite loop that will crash your app.
This brings us to our final rule.
Golden Rule #3 ππ½: You should only call the state setter function in response to an event, like a user interaction (e.g.,
onClick
,onChange
) or after an asynchronous task completes.
Putting It All Together: The Final, Working Code
Let's call our setResult
function inside our handleClick
handler. This is the correct way.
JavaScript
import { useState } from "react";
export default function App() {
// 1. Initialize state with a value and a setter function
const [result, setResult] = useState("Yes");
// 2. Create a function to handle the user's click
function handleClick() {
// 3. Use the setter function to update the state
setResult("Heck Yes!");
}
return (
<main>
<h1 className="title">Is state important to know?</h1>
{/* 4. Call the handler on click and display the current state */}
<button className="value" onClick={handleClick}>
{result}
</button>
</main>
);
}
Now, when you click the button, handleClick
is called. Inside handleClick
, setResult("Heck Yes!")
tells React two things:
Change the value of
result
to "Heck Yes!".Re-render the
App
component so the user can see the change.
ππ You Did It! ππ
You've now learned the core loop of interactivity in React: a component renders with initial state, a user interaction triggers an event handler, that handler uses a setter function to update the state, and React re-renders the component with the new state. Welcome to the world of React state!
Subscribe to my newsletter
Read articles from Nishi Surti directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Nishi Surti
Nishi Surti
Just a common developer like you ! Let's learn together and lift up each other.