React apps look like normal HTML pages, but they behave differently. When a user clicks a button, types in an input, selects a checkbox, or changes a dropdown, the UI should update immediately.
That is why we use state in React.
useState stores changing data inside a component. When we call its setter function—for example setCount, setUser, or setForm—React saves the next value, renders the component again, and updates only the required UI parts. State updates are applied for the next render, so logging a state variable immediately after calling its setter may still show the previous value.
What are React and Vite?
React is a JavaScript library for building user interfaces. Instead of manually finding HTML elements and changing them with DOM code, we describe how the UI should look for the current data. When state changes, React updates the screen to match that new state.
Vite is the tool used to create and run the React project quickly. It provides a fast development server and Hot Module Replacement (HMR), so changes in your code appear in the browser very quickly during development. It also creates an optimized production build.
For example, a Vite React project can start with:
npm create vite@latest my-react-app
cd my-react-app
npm install
npm run dev
**
Virtual DOM in Simple Words**
The browser has a real DOM—the actual HTML elements displayed on the page:
<h1>count: 0</h1>
When the count becomes 1, React does not need to reload the complete page manually.
React creates a lightweight JavaScript representation of the UI, often called the Virtual DOM. When state changes:
React creates the next UI representation.
React compares the previous UI with the new UI.
React finds what changed.
React updates the necessary part of the real browser DOM.
For example, in this code:
<h1>count: {count}</h1>
If count changes from 0 to 1, React updates the displayed count. Your header, buttons, form, and other components do not need a full page refresh.
This is why React feels dynamic and fast.
Why useState Is Important
The syntax is:
const [value, setValue] = useState(initialValue);
Example:
const [count, setCount] = useState(0);
Here:
- count is the current state value.
- setCount updates the value.
- 0 is the initial value.
When a button calls:
setCount(count + 1);
React stores the next count value and re-renders the component with the updated value. React’s official documentation describes useState as a Hook that adds a state variable to a component; its setter updates the state and triggers a re-render.
Your counter example:
const [count, setCount] = useState(0);
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<button onClick={() => setCount(0)}>
Reset
</button>
Real-world uses of useState
We use useState whenever data can change because of user actions or application events:
- Login form values: email, password, OTP.
- Like button: liked / not liked.
- Shopping cart quantity.
- Search input value.
- Dark mode / light mode.
- Modal open / close.
- Loading spinner: loading / completed.
- Selected language, country, state, city.
- Todo list items.
- API response data.
- Error messages and success messages. A normal JavaScript variable can store a value, but it does not tell React to render again. State does.
1. Toggle Button: ON and OFF
const [isOn, setIsOn] = useState(false);
A boolean has only two values:
true
false
That perfectly matches a two-state feature:
| UI meaning | Boolean value |
|---|---|
| OFF | false |
| ON | true |
Example:
import { useState } from "react";
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
return (
<div>
<button onClick={() => setIsOn(!isOn)}>
{isOn ? "ON" : "OFF"}
</button>
<p>The switch is {isOn ? "enabled" : "disabled"}.</p>
</div>
);
}
The best initial value is usually:
useState(false)
Because most switches begin in the OFF state unless the product requirement says otherwise.
Real-time use cases
Enable/disable notifications.
Dark mode switch.
Show password / hide password.
Turn on location access.
Online / offline availability.
Email subscription toggle.
Open / close mobile menu.
Your theme toggle is a real example:
checked={theme === "dark"}
onChange={toggleTheme}
The checkbox is checked only when the current theme is "dark".
2. Input Field Value: Why Use State?
Suppose the user types Vijay, and you want to display:
Hello, Vijay
const [name, setName] = useState("");
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
<p>Hello, {name}</p>
This is called a controlled input because React state controls the value of the input.
Why not use a normal variable?
This will not work correctly:
let name = "";
function handleChange(e) {
name = e.target.value;
}
The variable changes in JavaScript memory, but React does not know that it must update the UI. Therefore:
<p>Hello, {name}</p>
will not reliably show the latest name.
A normal variable also gets created again when the component re-renders. It is not a reliable place for UI data that must persist between renders.
The main difference
| Normal variable | useState |
|---|---|
| Value can change in JavaScript | Value can change in React |
| Does not request a re-render | Triggers a re-render |
| UI may not update | UI updates with the latest value |
| Not reliable for interactive UI data | Best for changing UI data |
Your password input already follows this idea:
const [user, setUser] = useState("");
<input
onInput={(e) => setUser(e.target.value)}
type="password"
/>
<p>hello, {user}</p>
A common React style is to use onChange:
onChange={(e) => setUser(e.target.value)}
Both can work in this case, but onChange is generally the standard choice for React form fields.
Real-time use cases
Search boxes.
- Login and registration forms.
- Live preview of a profile name.
- Chat message typing.
- Product filters.
- Coupon code input.
- Address forms.
- Comment boxes.
3. Show / Hide Text
For showing or hiding one element, boolean state is the best choice.
const [showText, setShowText] = useState(false);
Then render the paragraph only when showText is true:
<button onClick={() => setShowText(!showText)}>
{showText ? "Hide Text" : "Show Text"}
</button>
{showText && <p>This paragraph is visible now.</p>}
React supports conditional rendering using normal JavaScript conditions, including if, ternary operators, and &&
Why boolean is suitable
The question is simple:
Should this item be visible?
There are only two answers:
Yes → true
No → false
So a boolean is cleaner than using strings such as "show" and "hide".
Your password visibility code is the same pattern:
const [showText, setShowText] = useState(false);
const toggle = () => {
setShowText(!showText);
};
<input type={showText ? "text" : "password"} />
When showText is:
- false → password is hidden.
- true → password is visible.
Real-time use cases
- Password visibility.
- FAQ accordion.
- Sidebar open / close.
- Popup modal open / close.
- Read more / read less.
- Notification panel.
- Mobile navigation menu.
- Loading screen visibility. **
4. Character Counter
**
For a textarea, store the actual text in state:
const [message, setMessage] = useState("");
Then calculate the length from that text:
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Write your message..."
/>
<p>Character count: {message.length}</p>
If you want to ignore spaces at the beginning and end:
<p>Character count: {message.trim().length}</p>
Your code already does this:
<p>
character Count: {user.trim().length}
</p>
Should text and length both be in state?
Usually, no.
Do this:
const [text, setText] = useState("");
const length = text.length;
Avoid this:
const [text, setText] = useState("");
const [length, setLength] = useState(0);
Why? length can always be calculated from text. It is derived data.
If you store both separately, they can become inconsistent:
text = "Hello"
length = 2
That is incorrect state. Keeping only the text as state avoids this issue.
Real-time use cases
- Social media post character limit.
- Bio character count.
- SMS character limit.
- Product review textarea.
- Tweet-like post composer.
- Job application description.
- Comment section.
- AI prompt input limit. Example with a limit:
const [bio, setBio] = useState("");
const maxLength = 100;
<textarea
value={bio}
maxLength={maxLength}
onChange={(e) => setBio(e.target.value)}
/>
<p>
{bio.length} / {maxLength}
</p>
5. Form with Multiple Inputs
For a form with related fields such as name, email, phone, city, and password, one object state is usually clean and scalable.
const [form, setForm] = useState({
name: "",
email: "",
phone: "",
city: "",
password: "",
});
Use one reusable change handler:
`const handleChange = (e) => {
const { name, value } = e.target;
setForm({
...form,
name: value,
});
};`
Input example:
<input
type="text"
name="name"
value={form.name}
onChange={handleChange}
placeholder="Enter your name"
/>
<input
type="email"
name="email"
value={form.email}
onChange={handleChange}
placeholder="Enter your email"
/>
Your form implementation is already following this correct pattern.
Five separate states:
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [city, setCity] = useState("");
const [password, setPassword] = useState("");
This is valid, but it becomes repetitive as the form grows.
One object is easier for:
- Form submission.
- Sending data to an API.
- Resetting the entire form.
- Showing a preview.
- Adding more fields later.
For example:
console.log(JSON.stringify(form));
That produces a single object ready to send to a backend API:
{
"name": "Vijay",
"email": "vijay@email.com",
"phone": "9876543210",
"city": "Chennai",
"password": "secret123"
}
Common mistake: replacing the full object
This is wrong:
`setForm({
});`
If the user updates only the email field, all other fields disappear.
For example, before:
{
name: "Vijay",
email: "",
phone: "9876543210"
}
Wrong update:
setForm({
email: "vijay@email.com",
});
Result:
{
email: "vijay@email.com"
}
The name and phone values are lost.
Correct update:
setForm((previousForm) => ({
...previousForm,
[name]: value,
}));
Using the previous-state callback is especially safe when the new state depends on the old state. React also documents that state setters can receive an updater function based on the previous state.
Better version of your handler
const handleChange = (e) => {
const { name, value } = e.target;
setForm((previousForm) => ({
...previousForm,
[name]: value,
}));
};
Also, use this for the phone field:
type="tel"
Not:
type="tell"
Correct version:
<input
type="tel"
name="phone"
value={form.phone}
placeholder="Enter your phone"
onChange={handleChange}
/>
Real-time use cases
Registration form.
- Checkout and delivery address.
- Employee profile form.
- Bank account application.
- Contact us form.
- Resume builder.
- School admission form.
- Admin dashboard product form.
- Checkbox Selection List
For multiple selected skills, use an array state.
const [skills, setSkills] = useState([]);Why an array? Because a user may select multiple items:
["Reactjs", "Nodejs", "SQL"]`
When a checkbox is checked, add the skill. When unchecked, remove it.
`const handleSkillChange = (e) => {
const { value, checked } = e.target;
setSkills((previousSkills) =>
checked
? [...previousSkills, value]
: previousSkills.filter((skill) => skill !== value)
);
};
This is the same logic you used:
setSkills(
checked
? [...skills, value]
: skills.filter((skill) => skill !== value)
);
The callback version is preferred:
setSkills((previousSkills) =>
checked
? [...previousSkills, value]
: previousSkills.filter((skill) => skill !== value)
);
How it works
If the user selects React:
[]
becomes:
["Reactjs"]
Then selects Node.js:
["Reactjs"]
becomes:
["Reactjs", "nodejs"]
Then unselects React:
["Reactjs", "nodejs"]
becomes:
["nodejs"]
import { useState } from "react";
function Skills() {
const [skills, setSkills] = useState([]);
const handleSkillChange = (e) => {
const { value, checked } = e.target;
setSkills((previousSkills) =>
checked
? [...previousSkills, value]
: previousSkills.filter((skill) => skill !== value)
);
};
return (
<div>
<label>
<input
type="checkbox"
value="React"
onChange={handleSkillChange}
/>
React
</label>
<label>
<input
type="checkbox"
value="Node"
onChange={handleSkillChange}
/>
Node
</label>
<label>
<input
type="checkbox"
value="Java"
onChange={handleSkillChange}
/>
Java
</label>
<label>
<input
type="checkbox"
value="SQL"
onChange={handleSkillChange}
/>
SQL
</label>
<h3>Selected skills</h3>
<ul>
{skills.map((skill) => (
<li key={skill}>{skill}</li>
))}
</ul>
</div>
);
}
Notice this important improvement:
<li key={skill}>{skill}</li>
When rendering a list with .map(), React needs a stable key for each item. Since every selected skill is unique here, the skill text can be used as the key.
Real-time use cases
- Select technical skills in a job portal.
- Product category filters.
- Choose food preferences.
- Select permissions for users.
- Pick multiple courses.
- E-commerce filter: size, color, brand.
- Multi-select notification settings.
- Interests in a dating or social app.
- Dependent Dropdown: Country → State → City Dependent dropdown means that the second dropdown depends on the first, and the third depends on the second. Example:
Country → India
State → Tamil Nadu
City → Chennai
const locationData = {
India: {
"Tamil Nadu": ["Chennai", "Madurai", "Coimbatore"],
Kerala: ["Kochi", "Trivandrum"],
},
USA: {
California: ["Los Angeles", "San Diego"],
Texas: ["Houston", "Dallas"],
},
};
Use three state variables:
const [country, setCountry] = useState("");
const [state, setState] = useState("");
const [city, setCity] = useState("");
This is clear because each selected value has a separate meaning.
When country changes
When the user changes country:
- Update the country.
- Reset state to an empty string.
- Reset city to an empty string.
onChange={(e) => {
setCountry(e.target.value);
setState("");
setCity("");
}}
Why reset them?
Imagine this situation:
Country: India
State: Tamil Nadu
City: Chennai
Then the user selects:
Country: USA
Now Tamil Nadu and Chennai do not belong to the USA. So we must clear them.
When state changes
When the user selects a new state, reset only the city:
onChange={(e) => {
setState(e.target.value);
setCity("");
}}
Cleaner controlled dropdown version
<select
value={country}
onChange={(e) => {
setCountry(e.target.value);
setState("");
setCity("");
}}
>
<option value="">Select country</option>
{Object.keys(locationData).map((countryName) => (
<option key={countryName} value={countryName}>
{countryName}
</option>
))}
</select>
{country && (
<select
value={state}
onChange={(e) => {
setState(e.target.value);
setCity("");
}}
>
<option value="">Select state</option>
{Object.keys(locationData[country]).map((stateName) => (
<option key={stateName} value={stateName}>
{stateName}
</option>
))}
</select>
)}
{state && (
<select value={city} onChange={(e) => setCity(e.target.value)}>
<option value="">Select city</option>
{locationData[country][state].map((cityName) => (
<option key={cityName} value={cityName}>
{cityName}
</option>
))}
</select>
)}
The expressions:
{country && (...)}
and:
{state && (...)}
are conditional rendering. The state dropdown appears only after choosing a country; the city dropdown appears only after choosing a state. React supports this type of conditional UI using normal JavaScript logic.
Real-time use cases
- Country → State → City in an address form.
- Category → Subcategory → Product.
- Company → Department → Employee.
- University → Department → Course.
- Vehicle brand → Model → Variant.
- E-commerce category → type → item.
- Travel booking: country → city → hotel.
- Admin panel: role → permission → feature.
Top comments (0)