useState
useState in React explains local component state, setters, functional updaters, snapshots, batching, immutability, arrays and objects, and when not to store derived values in state.
useState is the most commonly used React Hook for managing local component state.
It allows a function component to remember information between renders and update the UI when that information changes.
Examples of state include:
- A counter value
- Text entered into an input
- Whether a menu is open
- Whether a modal is visible
- A selected tab
- A checkbox value
- Loading or error status
- Data selected by the user
useState↓Store component data↓State changes↓React schedules a render↓UI reflects the new state
What Is State?
State is data that can change over time and whose changes may affect what a component renders.
For example:
import { useState } from "react";function Counter() {const [count, setCount] = useState(0);return <p>Count: {count}</p>;}
Here:
countis the current state value.setCountis the function used to update the state.0is the initial state value.
When setCount updates the state, React renders the component again using the new value.
A useful mental model is:
State↓Render UI↓User interaction↓State update↓React renders again↓Updated UI
Importing useState
useState is imported from React:
import { useState } from "react";
Then it can be used inside a function component:
function Counter() {const [count, setCount] = useState(0);return <p>{count}</p>;}
Basic Syntax
The basic syntax is:
const [state, setState] = useState(initialValue);
For example:
const [count, setCount] = useState(0);
The Hook returns two values:
[currentState,stateUpdater]
Array destructuring gives these values meaningful names.
Conceptually:
const statePair = useState(0);const count = statePair[0];const setCount = statePair[1];
The normal form is:
const [count, setCount] = useState(0);
Understanding the Two Values
1. Current State
The first value represents the current state:
const [count, setCount] = useState(0);console.log(count);
Initially:
count = 0
After a state update, a subsequent render can see the new value.
2. State Setter
The second value is a function used to request a state update:
setCount(5);
or:
setCount(count + 1);
You should not directly modify state.
Incorrect:
count = 5;
Correct:
setCount(5);
React uses the setter to schedule the update and render the component with the new state.
A Complete Counter Example
import { useState } from "react";function Counter() {const [count, setCount] = useState(0);function increment() {setCount(count + 1);}function decrement() {setCount(count - 1);}return (<div><h2>Counter</h2><p>Count: {count}</p><button onClick={increment}>+</button><button onClick={decrement}>-</button></div>);}export default Counter;
The flow is:
Initial render↓count = 0↓User clicks +↓setCount(count + 1)↓React schedules update↓Component renders again↓count = 1↓UI displays 1
Why useState Is Needed
Consider a normal JavaScript variable:
function Counter() {let count = 0;function increment() {count++;}return (<div><p>{count}</p><button onClick={increment}>+</button></div>);}
The JavaScript variable can change, but changing it does not tell React to render the component again.
With useState:
const [count, setCount] = useState(0);
React can associate the value with the component and schedule a new render when the setter is called.
The important distinction is:
Normal variable↓JavaScript value changes↓No React state update
versus:
State↓Setter called↓React schedules update↓Component renders again
State and Rendering
React function components execute again when a state update causes a render.
function Counter() {const [count, setCount] = useState(0);console.log("Component rendered");return (<button onClick={() => setCount(count + 1)}>{count}</button>);}
Initial render:
Component renderedcount = 0
After clicking:
setCount(...)↓React schedules an update↓Component function runs again↓count = 1
A re-render does not mean the entire browser page is rebuilt from scratch. React determines what needs to change in the rendered output and updates the necessary DOM.
Initial State
The value passed to useState is used as the initial state:
const [name, setName] = useState("John");
const [age, setAge] = useState(25);
const [isOpen, setIsOpen] = useState(false);
const [items, setItems] = useState([]);
const [user, setUser] = useState(null);
Common initial values:
| Data | Example |
|---|---|
| Number | useState(0) |
| String | useState("") |
| Boolean | useState(false) |
| Array | useState([]) |
| Object | useState({}) |
| Empty value | useState(null) |
Choose an initial value that accurately represents the state before the component has received user input or other updates.
Lazy Initial State
If calculating the initial state is expensive, you can pass an initializer function:
const [items, setItems] = useState(() => createInitialItems());
React uses the result of the initializer as the initial state.
Compare:
const [items, setItems] = useState(createInitialItems());
with:
const [items, setItems] = useState(() => createInitialItems());
The second form tells React that createInitialItems is an initializer rather than a value that should be calculated on every render.
For simple values, this is unnecessary:
const [count, setCount] = useState(0);
Prefer the simple form unless initialization actually requires work.
Updating State
There are two common forms of state updates.
Direct Value Update
setName("Alice");
Use this when the next state does not need to be calculated from the previous state.
Functional Updater
setCount(previousCount => previousCount + 1);
Use this when the next state depends on the previous state.
For example:
setCount(previousCount => previousCount + 1);
The mental model is:
latest state↓apply update function↓new state
Why Functional Updates Matter
Consider:
function incrementThreeTimes() {setCount(count + 1);setCount(count + 1);setCount(count + 1);}
All three expressions use the count value from the current render.
If that render has:
count = 0
each update can effectively request:
0 + 1
A safer approach is:
function incrementThreeTimes() {setCount(previousCount => previousCount + 1);setCount(previousCount => previousCount + 1);setCount(previousCount => previousCount + 1);}
Now the updates can be processed sequentially:
0 → 1 → 2 → 3
Rule
If the next state depends on the previous state, prefer:
setState(previousState => /* calculate next state */);
This pattern is especially important for counters, toggles, queues, and other state transitions.
State Is a Snapshot
One of the most important React concepts is that state behaves like a snapshot for a particular render.
Consider:
function Counter() {const [count, setCount] = useState(0);function handleClick() {setCount(count + 1);console.log(count);}return <button onClick={handleClick}>{count}</button>;}
The console.log does not immediately show the new state value.
If the current render has:
count = 0
then the event handler sees:
count = 0
Calling:
setCount(1);
requests a future render.
It does not change the count variable inside the already-running function.
Mental model:
Current rendercount = 0↓setCount(1)↓request update↓current function continues↓next rendercount = 1
This explains why state should not be treated like an ordinary mutable JavaScript variable.
State Updates Are Requests for a New Render
A useful way to think about:
setCount(10);
is:
React, please use 10 as the state value for a future render.
It is not:
count = 10 immediately
This distinction becomes especially important when several updates, event handlers, or asynchronous operations are involved.
React Batches State Updates
React can group multiple state updates together to process them efficiently.
For example:
function handleClick() {setFirstName("John");setLastName("Doe");}
React can process these updates together and render the resulting UI efficiently.
Therefore, think of state setters as requests that participate in React's update process rather than synchronous assignments to local variables.
State With Strings
State can store text:
function Greeting() {const [name, setName] = useState("");return (<div><inputvalue={name}onChange={event => setName(event.target.value)}/><p>Hello, {name}</p></div>);}
The flow is:
User types↓onChange event↓setName(...)↓state update↓render↓UI reflects new name
This pattern is commonly used with controlled form components.
State With Booleans
Boolean state is useful for toggles and visibility.
function Menu() {const [isOpen, setIsOpen] = useState(false);function toggleMenu() {setIsOpen(previousOpen => !previousOpen);}return (<div><button onClick={toggleMenu}>{isOpen ? "Close" : "Open"}</button>{isOpen && <nav>Menu content</nav>}</div>);}
The state represents:
false → menu closedtrue → menu open
Because the next value depends on the current value, the functional updater is a good choice:
setIsOpen(previousOpen => !previousOpen);
State With Arrays
State can store arrays:
const [items, setItems] = useState([]);
To add an item:
setItems(previousItems => [...previousItems,"React"]);
The spread operator creates a new array.
Avoid mutating the existing array:
items.push("React");setItems(items);
Prefer:
setItems(previousItems => [...previousItems,"React"]);
React state should be updated using new values rather than directly mutating existing arrays.
Removing an Item From an Array
For example:
function TodoList() {const [items, setItems] = useState([{ id: 1, text: "Learn React" },{ id: 2, text: "Practice Hooks" }]);function removeItem(id) {setItems(previousItems =>previousItems.filter(item => item.id !== id));}return (<ul>{items.map(item => (<li key={item.id}>{item.text}<button onClick={() => removeItem(item.id)}>Remove</button></li>))}</ul>);}
The filter method creates a new array without the removed item.
State With Objects
State can store objects:
const [user, setUser] = useState({name: "John",age: 25});
To update only the name:
setUser(previousUser => ({...previousUser,name: "Alice"}));
The spread operator preserves the existing properties.
Result:
{name: "Alice",age: 25}
Avoid assuming that object state is automatically merged.
For example:
setUser({name: "Alice"});
replaces the state value with a new object containing only name.
If you want to preserve existing properties, explicitly copy them:
setUser(previousUser => ({...previousUser,name: "Alice"}));
Nested Objects
For nested state, copy each object level that needs to be changed.
Example:
const [user, setUser] = useState({name: "John",address: {city: "Cuttack",country: "India"}});
To update the city:
setUser(previousUser => ({...previousUser,address: {...previousUser.address,city: "Bhubaneswar"}}));
The mental model is:
New user object↓New address object↓Updated city
For deeply nested state, consider whether the state structure can be simplified.
Multiple State Variables
A component can have multiple pieces of state:
function Profile() {const [name, setName] = useState("");const [age, setAge] = useState(0);const [isEditing, setIsEditing] = useState(false);return (<div><p>{name}</p><p>{age}</p><p>{isEditing ? "Editing" : "Viewing"}</p></div>);}
Each useState call represents an independent state value.
Conceptually:
Profile component│├── name├── age└── isEditing
This can make independent values easier to understand and update.
Grouping Related State
Sometimes several values represent one logical object.
For example:
const [form, setForm] = useState({name: "",email: "",city: ""});
This can be appropriate when the values are closely related.
Updating one property:
setForm(previousForm => ({...previousForm,email: "user@example.com"}));
However, grouping everything into one object is not automatically better.
Prefer separate state when values have clearly independent behavior:
const [isOpen, setIsOpen] = useState(false);const [selectedTab, setSelectedTab] = useState("overview");
Choose the structure that makes the state transitions easiest to understand.
Avoid Unnecessary State
Not every value needs to be stored in state.
Suppose you have:
function UserProfile() {const [firstName, setFirstName] = useState("John");const [lastName, setLastName] = useState("Doe");const fullName = `${firstName} ${lastName}`;return <h2>{fullName}</h2>;}
fullName does not need its own state because it can be calculated from existing state.
Avoid:
const [fullName, setFullName] = useState("John Doe");
when it can simply be derived:
const fullName = `${firstName} ${lastName}`;
A useful rule is:
If a value can be calculated reliably from existing props or state, it often does not need separate state.
State Should Represent Information That Changes
Good candidates for state:
User inputSelectionVisibilityToggle statusCurrent stepLoading statusFetched dataTemporary UI state
Poor candidates for state:
Values that never changeValues directly derived from other stateValues that can be calculated during renderingConstantsSimple formatting results
For example:
const price = 100;const taxRate = 0.18;const total = price + price * taxRate;
There is no reason to make total state if it is always derived from price and taxRate.
Do Not Store Props in State Without a Reason
Consider:
function User({ name }) {const [userName, setUserName] = useState(name);return <h2>{userName}</h2>;}
This creates two sources of information:
props.namestate.userName
If the prop changes later, the state does not automatically synchronize with it.
Often the simpler solution is:
function User({ name }) {return <h2>{name}</h2>;}
Use state when the component needs its own independently changing value, not simply because a prop exists.
State and Props
Props and state both influence rendering, but they have different purposes.
| Props | State |
|---|---|
| Passed into a component | Managed by the component |
| Read-only from the receiving component | Updated using a setter |
| Controlled by the parent | Controlled by the component |
| Used to configure a component | Used for changing component data |
Example:
function Counter({ initialCount }) {const [count, setCount] = useState(initialCount);return (<button onClick={() => setCount(previous => previous + 1)}>{count}</button>);}
Here:
initialCount → propcount → state
The parent supplies the starting value, while the component manages subsequent changes.
State and Event Handling
useState is commonly combined with event handlers.
function LikeButton() {const [liked, setLiked] = useState(false);function handleClick() {setLiked(previousLiked => !previousLiked);}return (<button onClick={handleClick}>{liked ? "Liked" : "Like"}</button>);}
The relationship is:
User event↓Event handler↓State setter↓New state↓Render↓Updated UI
This pattern is fundamental to interactive React applications.
State and Conditional Rendering
State can determine which UI is rendered.
function LoginStatus() {const [isLoggedIn, setIsLoggedIn] = useState(false);return (<div>{isLoggedIn ? (<p>Welcome back!</p>) : (<p>Please log in.</p>)}<buttononClick={() => setIsLoggedIn(previous => !previous)}>Toggle Login</button></div>);}
The state controls the UI:
isLoggedIn = false↓"Please log in."isLoggedIn = true↓"Welcome back!"
Resetting State
Sometimes a component needs to reset state.
function Form() {const [name, setName] = useState("");function resetForm() {setName("");}return (<div><inputvalue={name}onChange={event => setName(event.target.value)}/><button onClick={resetForm}>Reset</button></div>);}
Resetting is simply another state update:
setName("");
For multiple values:
setForm({name: "",email: "",city: ""});
State Initializers Are Not Reset Values
The initial value passed to useState is used to initialize the state.
const [count, setCount] = useState(10);
Calling the component again as part of a normal re-render does not mean the state becomes 10 again.
Once initialized, the state persists between renders.
To reset it explicitly:
setCount(10);
This distinction is important:
Initial value↓Used to initialize stateState setter↓Used to change state later
Preserving State Between Renders
When a component re-renders, React preserves its state when the component remains in the same position in the UI tree.
For example:
function Counter() {const [count, setCount] = useState(0);return (<button onClick={() => setCount(count + 1)}>{count}</button>);}
A normal state update does not reset count to 0.
Instead:
Initial render → 0Update → 1Update → 2Update → 3
State is associated with the component's position in the rendered tree, not simply with the local variable declaration.
State and Component Identity
Consider:
function App() {return <Counter />;}
As long as React considers the Counter component to be the same component in the same position, its state can be preserved between renders.
Changing component identity or using different key values can cause state to be reset.
This becomes especially important when working with lists and conditional component trees.
State and Immutability
React state should generally be treated as immutable.
For arrays:
setItems(previousItems => [...previousItems,newItem]);
For objects:
setUser(previousUser => ({...previousUser,name: "Alice"}));
The goal is to create a new state value instead of directly changing the existing one.
Avoid:
user.name = "Alice";
or:
items.push(newItem);
Instead, create the next state:
setUser(previousUser => ({...previousUser,name: "Alice"}));
Comparing Old and New State
React can use the identity of state values to determine whether an update results in a different value.
For primitive values:
setCount(5);
For objects and arrays, creating a new reference is generally important:
setUser(previousUser => ({...previousUser,name: "Alice"}));
rather than mutating the existing object.
This is one reason immutable update patterns are so common in React.
Functional Updaters for Reliable Transitions
Functional updates are useful whenever state changes are expressed as transitions.
Counter:
setCount(previous => previous + 1);
Toggle:
setOpen(previous => !previous);
Array addition:
setItems(previous => [...previous, item]);
Array removal:
setItems(previous =>previous.filter(item => item.id !== id));
Object update:
setUser(previous => ({...previous,active: true}));
The common pattern is:
previous state↓calculate next state↓return next state
Common useState Mistakes
Mistake 1: Mutating State Directly
Incorrect:
count++;
Correct:
setCount(previous => previous + 1);
Mistake 2: Mutating Arrays
Incorrect:
items.push("React");setItems(items);
Correct:
setItems(previous => [...previous,"React"]);
Mistake 3: Mutating Objects
Incorrect:
user.name = "Alice";setUser(user);
Correct:
setUser(previous => ({...previous,name: "Alice"}));
Mistake 4: Expecting Immediate State Changes
Incorrect mental model:
setCount(10);console.log(count); // expecting 10
The current render's count value does not change in place.
Think:
setCount(10)↓request next state↓future render
Mistake 5: Using Current State for Repeated Updates
Instead of:
setCount(count + 1);setCount(count + 1);
prefer:
setCount(previous => previous + 1);setCount(previous => previous + 1);
when each update depends on the previous result.
Mistake 6: Storing Derived Data
Avoid:
const [firstName, setFirstName] = useState("John");const [lastName, setLastName] = useState("Doe");const [fullName, setFullName] = useState("John Doe");
Prefer:
const [firstName, setFirstName] = useState("John");const [lastName, setLastName] = useState("Doe");const fullName = `${firstName} ${lastName}`;
This avoids duplicated sources of truth.
Mistake 7: Putting Too Much Into One State Object
This:
const [state, setState] = useState({name: "",email: "",isOpen: false,selectedTab: "home",loading: false});
may become difficult to manage if these values have unrelated behavior.
Sometimes separate state is clearer:
const [name, setName] = useState("");const [email, setEmail] = useState("");const [isOpen, setIsOpen] = useState(false);const [selectedTab, setSelectedTab] = useState("home");const [loading, setLoading] = useState(false);
The right choice depends on how the state changes together.
Practical Example: Character Counter
import { useState } from "react";function CharacterCounter() {const [text, setText] = useState("");return (<div><textareavalue={text}onChange={event => setText(event.target.value)}/><p>Characters: {text.length}</p></div>);}export default CharacterCounter;
Notice that the character count does not need its own state.
const characterCount = text.length;
This is derived data.
Practical Example: Todo List
import { useState } from "react";function TodoList() {const [text, setText] = useState("");const [todos, setTodos] = useState([]);function addTodo() {if (!text.trim()) {return;}const todo = {id: Date.now(),text: text.trim()};setTodos(previousTodos => [...previousTodos,todo]);setText("");}function removeTodo(id) {setTodos(previousTodos =>previousTodos.filter(todo => todo.id !== id));}return (<div><inputvalue={text}onChange={event => setText(event.target.value)}placeholder="Enter a task"/><button onClick={addTodo}>Add</button><ul>{todos.map(todo => (<li key={todo.id}>{todo.text}<button onClick={() => removeTodo(todo.id)}>Remove</button></li>))}</ul></div>);}export default TodoList;
This example demonstrates:
- String state
- Array state
- Functional state updates
- Immutable array updates
- Derived UI
- Event handling
- Controlled input
- State reset
Practical Example: Form State
A small form can use an object as state:
import { useState } from "react";function UserForm() {const [form, setForm] = useState({name: "",email: ""});function handleChange(event) {const { name, value } = event.target;setForm(previousForm => ({...previousForm,[name]: value}));}function handleSubmit(event) {event.preventDefault();console.log(form);}return (<form onSubmit={handleSubmit}><inputname="name"value={form.name}onChange={handleChange}placeholder="Name"/><inputname="email"value={form.email}onChange={handleChange}placeholder="Email"/><button type="submit">Submit</button></form>);}export default UserForm;
The computed property:
[name]: value
allows one handler to update different fields.
Practical Example: Shopping Cart Quantity
import { useState } from "react";function CartItem() {const [quantity, setQuantity] = useState(1);function increase() {setQuantity(previous => previous + 1);}function decrease() {setQuantity(previous => Math.max(1, previous - 1));}return (<div><button onClick={decrease}>-</button><span>{quantity}</span><button onClick={increase}>+</button></div>);}export default CartItem;
The updater form makes the state transition explicit:
quantity↓previous quantity↓+1 or -1↓next quantity
When Should You Use useState?
Use useState when:
- A value changes over time.
- The value affects rendered UI.
- The state belongs to a particular component or small component subtree.
- User interactions should update the value.
- The component needs to remember information between renders.
Examples:
✓ Input value✓ Open/closed state✓ Selected option✓ Counter✓ Local form data✓ Current tab✓ Temporary UI state
When Should You Not Use useState?
You may not need useState when:
The value is constant
const appName = "React App";
The value can be derived
const fullName = `${firstName} ${lastName}`;
The value does not affect rendering
Some values are better suited to useRef, which will be covered in a later lesson.
The state belongs to another component
Sometimes state should be lifted to a parent component and passed down as props.
These decisions become increasingly important as React applications grow.
useState and State Ownership
A key architectural question is:
Which component should own this state?
Suppose two components need access to the same selected product:
App├── ProductList└── ProductDetails
If both need the selected product, the state may belong in App:
function App() {const [selectedProduct, setSelectedProduct] = useState(null);return (<><ProductListonSelect={setSelectedProduct}/><ProductDetailsproduct={selectedProduct}/></>);}
This is an introduction to lifting state up, an important React design pattern.
State and Reusability
Good component design usually keeps state close to where it is needed.
For example, a dropdown's open/closed state may belong inside the dropdown:
function Dropdown() {const [isOpen, setIsOpen] = useState(false);// ...}
If unrelated components need the same state, it may need to move higher in the component tree.
The goal is not to put all state at the top.
Instead:
Keep state as local as possible↓Lift it when multiple components need it
Best Practices for useState
1. Give State Meaningful Names
Prefer:
const [isOpen, setIsOpen] = useState(false);
over:
const [x, setX] = useState(false);
2. Match the Initial Value to the Data
const [items, setItems] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState(null);
3. Use Functional Updates When Based on Previous State
setCount(previous => previous + 1);
4. Avoid Direct Mutation
Create new arrays and objects when updating them.
5. Avoid Duplicate State
If data can be derived, calculate it rather than storing a second copy.
6. Keep State Local When Possible
Do not move state to a global solution unless the application's requirements actually call for it.
7. Keep State Structure Simple
State should make the component easier to reason about, not harder.
useState Mental Model
A useful mental model is:
┌───────────────┐│ State │└───────┬───────┘│▼┌───────────────┐│ Render │└───────┬───────┘│▼┌───────────────┐│ UI │└───────┬───────┘│User interaction│▼┌───────────────┐│ setState(...) │└───────┬───────┘│▼┌───────────────┐│ Next Render │└───────────────┘
The most important concept is:
State drives rendering, and state setters request the next state.
Quick Comparison
| Approach | Purpose |
|---|---|
let value | Temporary JavaScript variable |
const value | Constant reference |
useState | State that participates in rendering |
useRef | Persistent mutable value that does not itself trigger a render |
| Props | Data passed into a component |
A later lesson will explore useRef in detail.
Interview questions & answers
Interview preparation
10 questions grouped by difficulty — expand any card for a model answer and follow-up probe.
Beginner
1What is useState?
BeginnerModel answer
useStateis a React Hook that allows function components to manage state.2What is the difference between state and a normal variable?
BeginnerModel answer
A normal variable does not persist as component state between renders and changing it does not tell React to render updated UI.
State is preserved by React and updating it can trigger a render.
3Does useState immediately change the current state variable?
BeginnerModel answer
No.
Calling the setter requests an update for a future render.
The current render continues with its existing state snapshot.
4What is lazy initialization?
BeginnerModel answer
Instead of passing an already calculated value, you can provide an initializer function:const [value, setValue] = useState(() => calculateInitialValue());This is useful when initial-state calculation is expensive.
Intermediate
5What does useState return?
IntermediateModel answer
It returns an array containing:[currentState, stateSetter]6When should you use the functional updater form?
IntermediateModel answer
When the next state depends on the previous state:setCount(previous => previous + 1);7Can a component have multiple useState calls?
IntermediateModel answer
Yes.const [name, setName] = useState(""); → const [age, setAge] = useState(0); → const [isOpen, setIsOpen] = useState(false);
Advanced
8Why should you use the setter instead of modifying state directly?
AdvancedModel answer
The setter tells React that the state should change and allows React to schedule a new render.
9Why shouldn't you mutate arrays or objects in state?
AdvancedModel answer
Direct mutation can produce incorrect update patterns and makes state changes harder for React and developers to reason about.
Prefer creating new arrays or objects.
10Should derived values always be stored in state?
AdvancedModel answer
No.
If a value can be calculated from existing props or state, it often should be derived during rendering instead.
Practice Exercises
Exercise 1: Counter
Create a counter with:
- Increment button
- Decrement button
- Reset button
- Current count display
Use functional updates where appropriate.
Exercise 2: Toggle
Create a component that:
- Displays "ON" or "OFF"
- Has a toggle button
- Uses boolean state
Exercise 3: Character Counter
Create a textarea that:
- Stores its value in state
- Displays the current character count
- Updates as the user types
Do not create separate state for the character count.
Exercise 4: Todo List
Create a todo list that supports:
- Adding todos
- Removing todos
- Displaying all todos
- Clearing the input after adding
Use immutable array updates.
Exercise 5: User Form
Create a form with:
- Name
- City
Store the form values in an object state and update fields using a reusable change handler.
Exercise 6: Shopping Cart Quantity
Create a quantity selector with:
- Increment
- Decrement
- Minimum quantity of
1
Use functional state updates.
Mini Project: Like Button
Build a reusable Like Button component.
Requirements:
Initial state:liked = falseClick:false → truetrue → false
Display:
♡ Like
when not liked, and:
♥ Liked
when liked.
A possible implementation:
import { useState } from "react";function LikeButton() {const [liked, setLiked] = useState(false);function handleClick() {setLiked(previousLiked => !previousLiked);}return (<button onClick={handleClick}>{liked ? "♥ Liked" : "♡ Like"}</button>);}export default LikeButton;
This small example demonstrates the complete state cycle:
Initial state↓false↓User clicks↓setLiked(...)↓true↓React renders↓UI changes
Quick Cheat Sheet
Import
import { useState } from "react";
Basic state
const [count, setCount] = useState(0);
Set a value
setCount(10);
Update from previous state
setCount(previous => previous + 1);
Boolean toggle
setOpen(previous => !previous);
Array update
setItems(previous => [...previous,item]);
Array removal
setItems(previous =>previous.filter(item => item.id !== id));
Object update
setUser(previous => ({...previous,name: "Alice"}));
Avoid mutation
// Avoiditems.push(item);// PrefersetItems(previous => [...previous, item]);
Derived value
const fullName = `${firstName} ${lastName}`;