React useState Hook Tutorial: Complete Guide to Component State Management
Learn how React components maintain and update their state, allowing you to create dynamic and interactive user interfaces.
Introduction
Modern web applications are highly interactive, users can click on buttons, fill forms, toggle navbars, play a video and so on.
Interactivity
makes the UI
, therefore the React
components change as the time wheel rolls by.
When you type on an input
field, the wrapper component should keep track of the last typed value
.
If you toggle a navbar
, it should always stay toggled and never get reset back to its previous state as long as we’ve never clicked on the toggle button
again.
In other words, Sometimes a React
component needs some kind of local and personal memory to remember what’s needed to accomplish its mission.
Understanding Component State
This specific type of memory is called a component’s local state, and it’s today’s article topic. So without any further ado let’s get started.
Building a Counter Component
Let’s say that we want to create a basic Counter
component, that supports incrementation, decrementation and reset operations.
Or in other words, a React
component that renders a count variable, while providing buttons for incrementing, decrementing and resetting that Counter
.
The count
variable is declared locally using the let keyword, and then rendered in the JSX via the curly brackets syntax.
function App(){
let counter;
console.count("UI: Updated");
return (
<div>
<div>Count is {counter}</div>
<div>
<button onClick={(e)=>{
counter += 1;
console.log(counter)
}}>Increment</button>
<button onClick={(e)=>{
counter -= 1;
console.log(counter)
}}>Decrement</button>
<button onClick={(e)=>{
counter = 0;
console.log(counter)
}}>Reset</button>
</div>
</div>
)
}
Each button
is attached an event handler
that change the counter
variable depending on the operation.
Adding one on incrementation, subtracting one on the decrementation and resetting the variable to zero when the reset button gets clicked.
Now, let’s try to increment the counter
while checking the console logs in the inspect window in parallel.

The counter
variable is indeed getting incremented with every click, but the displayed counter value in the UI is stuck at 0.
In other words, the component is not re-rendering the JSX
when we increment the variable.
Notice also that the
UI: Updated
message got printed two times meaning that the component have only rendered one time. By defaultReact
usesStrict mode
in developement environment for debuging reseaons, that’s why the message got printed twice. In production environment,Strict mode
gets disabled, thus only 1 render will onccur.
Come on, How it’s even possible to call this framework React
if it’s not reacting to the counter
variable changes by re-rendering the component and updating the UI
.

Well the reason behind that, is that changes to the locally declared counter
variable
can’t trigger a re-render. Or in other terms, no one is telling react that the counter
variable has changed.
Even if we suppose that changing the counter
variable directly, will trigger a component’s re-render.
The counter
variable will be stuck at zero.
The reason is when the component’s code runs the counter
variable will be re-declared again Therefore re-initialized to zero.
So to summarize all what have been said, we need two built-in mechanisms to make the counter example work:
- Something that tells
React
that a givenstate variable
has changed, therefore triggering a component’s re-render, then updating theUI
. - And a way to persist data or
state
between the component’s re-renders, so that ourcounter
variable value will never get reset or lost between re-renders again.
Fortunately React has to React
on this and provide a utility function named useState
.

Using useState
import {useState} from "react"
function App(){
const stateTuple = useState()
const [state, setState] = stateTuple
}
useState
can be imported from "react"
and used inside any component to declare a local state.
useState
returns what is known as a tuple
or in other words an array of two items, the first being the state
variable that persist between re-renders, and the second one being a setter function setState
that update the state while triggering a re-render.
To make this more convenient we usually use the array destructuring syntax, to store the two returned array items in two different variables without a lot of boilerplate code.
import {useState} from "react"
function App(){
const [state, setState] = useState()
}
The items are usually named variableName
followed by setVariableName
but you’re free to name them as you prefer.
It’s also worth mentioning that useState
accepts an argument that consist of the initial value of the state, for example in our case we want to declare a state variable named counter
, a corresponding setter function named setState
while ensuring that the count
variable defaults to zero.
const [counter, setCounter] = useState(0)
let’s refactor the event handlers to set the counter
using the setCounter
setter function instead of mutating the variable directly.
function App(){
import {useState} from 'react';
const [counter, setCounter] = useState(0)
console.count("UI: Updated")
return (
<div>
<div>Count is {counter}</div>
<div>
<button onClick={()=>{
setCounter(counter+1)
}}>Increment</button>
<button onClick={()=>{
setCounter(counter-1)
}}>Decrement</button>
<button onClick={()=>{
setCounter(0)
}}>Reset</button>
</div>
</div>
)
}
Now, Let’s try to increment, decrement and reset the counter
.

Unlike the previous example, now the UI
is reacting to the counter
variable updates and changing whenever the counter variable gets modified.
Or in other terms, the component re-renders when the counter variable gets modified with the setter function.
Even though the code inside the component re-runs entirely when re-rendering, the state still get persisted between all the re-renders.
Hooks Rules
In React
, any function starting with use
is called a hook
.
Therefore useState
is one of React
’s built-in hooks
.
In addition to the default
hooks
that get shipped with react you’re free to create your own but that’s another topic for another article.
It’s true that hooks
are ordinary Javascript
functions, but you can’t use them everywhere in your Javascript
application.
Any happy marriage implies adhering to a bunch of pre-defined rules and so too does your relationship with React
hooks
.
The first rule is to always and always call your React
hooks at the top level of your component.
In other words, just after the opening curly brackets {}
of the component’s function declaration.
First Rule
Don’t even think about using hooks inside:
Loops, conditions, nested functions, try/catch/finally blocks, or JSX
Markup
.
import { useState } from "react"
function App(){
// Loops ❌
while(true){ // ❌
const [counter,setCounter] = useState(0)
}
for(let i=0;i<5;i++){ // ❌
const [counter,setCounter] = useState(0)
}
do{ // ❌
const [counter,setCounter] = useState(0)
}while(true)
}
import { useState } from "react"
function App(){
if(true){ // Conditions ❌
const [counter,setCounter] = useState(0)
}
}
import { useState } from "react"
function App(){
// Try Catch blocks
try{ // Try Catch, Finally blocks ❌
const [counter,setCounter] = useState(0) // Try Catch blocks ❌
}catch(error){
const [counter,setCounter] = useState(0) // Try Catch blocks ❌
}finally{
const [counter,setCounter] = useState(0) // Try Catch, Finally blocks ❌
}
}
Instead always use them at the top level of your function component before any early return
statement.
import {useState} from "react"
function App(){
const [counter,setCounter] = useState(0) // ✅
if(true){ // Early return
return null
}
const [counter,setCounter] = useState(0) // ❌
}
Usually, React
will let you know when you’ve broken one of these rules with a detailed error message,
Second Rule
The second rule, is to never use hooks
in any other place other than a React
function component.
Using them in an ordinary function, class
or object
will only cause you frustration and trouble.
import {useState} from "react"
function App(){
const [counter,setCounter] = useState(0) // ✅
}
function add(a,b){
const [counter,setCounter] = useState(0) // ❌
return a + b;
}
class Counter{
const [counter,setCounter] = useState(0) // ❌
}
const calculator = {
add:(a,b)=>{
const [counter,setCounter] = useState(0) // ❌
}
}
How State Updates Work
Now let’s get back to our previous counter
example, and break what’s happening under the hood slowly.
When we click on the increment button
the click event
gets fired, therefore the event handler will start running.
Inside the event handler’s code the setCounter
function gets called with the current counter
state value which is equal to zero plus one as an argument.
setCounter(counter + 1) // counter=0
Calling the latter triggers a second re-render or tells React
: “Hey, some state got updated here, please re-render the component and then update the UI
”.
Even though the setCounter
function executes on the current render the counter state value will remain equal to 0 until the next render.
function App(){
// First Render
//...code
// counter = 0
return (
<div>
<div>Count is {counter} {/* 0 */}</div>
<div>
<button onClick={()=>{
setCounter(counter+1) // setCounter(0+1)
// counter = 0 , It's scheduled to change on the Second render
}}>Increment</button>
{/** ... code */}
</div>
</div>
)
}
In other terms React
queues, all the updates of the current in memory until the next render happen where the UI
will get constructed depending on the new state value.
On the second render the counter
state will be incremented therefore it will be equal to 1, the component will execute from the top to the bottom returning the JSX
with the updated counter
state value, and finally React
will update the react DOM
in the user’s browser.
function App(){
// Second Render
//...code
// counter = 1
return (
<div>
<div>Count is {counter} {/* 1 */}</div>
<div>
<button onClick={()=>{
setCounter(counter+1) // setCounter(1+1)
// counter = 1 , It's scheduled to change on the Third render
}}>Increment</button>
{/** ... code */}
</div>
</div>
)
}
Multiple Components and State
Let me ask you a question now. What would you expect if we have called the Counter
component two times in the App
component? What would happen if we increment one of them?
function Counter(){
/*Previous counter code*/
}
function App(){
return(
<div>
<Counter/> {/* counter = 1*/}
<Counter/> {/* counter = 0 */}
</div>
)
}
As we have said, from the beginning the state is private and personal. So incrementing the first counter will only affect the first called component.
Multiple State Variables
Another question that may traverse your mind is can we use more than one state in a React
a component.
The answer is absolutely yes, we can do that.
Let’s take this Greeting
component as an example.
function UserGreeting() {
const [name, setName] = useState('Guest');
const [showGreeting, setShowGreeting] = useState(true);
return (
<div>
{showGreeting && <p>Hello, {name}!</p>}
<button onClick={() => setName(name === 'Guest' ? 'User' : 'Guest')}>
Toggle Name
</button>
<button onClick={() => setShowGreeting(!showGreeting)}>
{showGreeting ? 'Hide' : 'Show'} Greeting
</button>
</div>
);
}
Here we are declaring two pieces of states: one holding the name
which can be either Guest
or User
and the other one is a boolean
named isGuest
controlling whether we show the greeting message or not.
const [name, setName] = useState('Guest');
const [showGreeting, setShowGreeting] = useState(true);
The component conditionally renders a greeting message at the top along with two action buttons at the bottom.
- The Greating message is only shown when the
showGreeting
state is set totrue
. The message consists of ap
tag wrapping aHello
string and the currentname
state value.
{showGreeting && <p>Hello, {name}!</p>}
- The first action button, is responsible on toggling the
name
state betweenGuest
andUser
.
<button onClick={() => setName(name === 'Guest' ? 'User' : 'Guest')}>
Toggle Name
</button>
- The second one, toggles the
showGreeting
state betweentrue
andfalse
.
<button onClick={() => setShowGreeting(!showGreeting)}>
{showGreeting ? 'Hide' : 'Show'} Greeting
</button>
React
is smart enough to determine which state variable corresponds to which useState
call as long as you follow the law of hooks.
React
is internally relying on the order ofuseState
calls.
With all that being said, we can conclude that Using two states
or more is a completely viable and easily achievable option in React
.
Conclusion
In the next article we will be diving deeper into how React
goes from rendering to displaying the UI
in the user’s browser screen.
Thank you for your attentive reading and happy coding 🧑💻!