Module 3.1: useState Deep Dive - How it Works & Immutability

🎯 Learning Objectives
By the end of this module, you’ll have a solid grasp of:
- One-way Data Flow: Understanding how data moves in a single direction within React.
- State vs. Local Variables: Why regular variables can’t trigger UI updates (re-renders).
- Immutability: The golden rule for updating
ObjectorArraystate. We’ll help you avoid the commonmutating statepitfalls.
1. One-way Data Flow
React orchestrates data flow in one single direction: from Parent components down to Child components, passed via Props.

- The Upside: This makes debugging a breeze and gives you crystal-clear control over your application’s state.
- The Rule: Child components cannot directly modify data they receive from their parent. If a change is needed, the parent must provide a callback function (a setter function) for the child to use.
2. State vs. Local Variables
A common stumbling block for newcomers (newbies) is trying to use regular variables (let, var) to manage data that’s meant to be displayed on the UI.

Here’s the Catch:
- Regular Variables Don’t Trigger Re-renders: React isn’t notified when a regular variable changes, so it has no reason to update the interface.
- Local Variables Get “Reset” on Re-render: Every time your component re-renders (perhaps due to a different state changing), the component function runs from the top. This means your local variables are re-initialized to their default values.
Example Code (WRONG):
function WrongCounter() {
let count = 0; // Local variable
const handleClick = () => {
count = count + 1;
console.log(count); // The log will increase, but the UI won't change
};
return <button onClick={handleClick}>{count}</button>;
}Example Code (CORRECT):
function RightCounter() {
const [count, setCount] = useState(0); // State
const handleClick = () => {
setCount(count + 1); // This triggers a re-render
};
return <button onClick={handleClick}>{count}</button>;
}3. Immutability
This is arguably the MOST CRITICAL concept when working with useState and reference types like Objects or Arrays.
The Principle: NEVER directly modify (mutate) your existing state. Instead, create a new copy, make your changes to that copy, and then update the state with this fresh copy.

3.1. Why Does This Matter?
React compares state by looking at memory addresses (Reference comparison: prevObj === nextObj). If you mutate an object or array directly (e.g., arr.push()), its memory address remains the same. React sees the address hasn’t changed and assumes the state is identical, thus skipping the re-render.
3.2. Handling Objects
Leverage the Spread syntax (...) to copy properties.
const [user, setUser] = useState({ name: 'Luan', age: 25 });
// ❌ WRONG: Direct mutation
// user.name = 'Tien';
// setUser(user); // React sees 'user' is the same object -> No re-render
// ✅ CORRECT: Create a new object
setUser({
...user, // Copy all existing properties
name: 'Tien', // Overwrite the property you want to change
});3.3. Handling Arrays
Absolutely avoid: .push(), .pop(), .splice(), .sort(), .reverse() (because they modify the original array in place).
Instead, opt for: .map(), .filter(), and the ...spread syntax.
const [todos, setTodos] = useState(['Learn React', 'Learn UI']);
// Adding an item (Create)
// ❌ WRONG: todos.push('New task');
// ✅ CORRECT:
setTodos([...todos, 'New Task']);
// Removing an item (Delete) - Remove 'Learn React'
// ✅ CORRECT:
setTodos(todos.filter((t) => t !== 'Learn React'));
// Updating an item (Update) - Change 'Learn UI' to 'Master UI'
// ✅ CORRECT:
setTodos(todos.map((t) => (t === 'Learn UI' ? 'Master UI' : t)));4. Hands-On Practice (Labs)
4.1. Lab 1: Coder Profile Editor (Object State)
Build a form to edit a programmer’s profile.
- Your state should be an object:
{ name: "Dan", company: "Meta", stack: "Frontend" }. - Include three input fields to modify each property.
- The Challenge: When you edit the “name”, ensure the “company” and “stack” aren’t lost.

💡 Pro Tip:
- Use the Spread Operator
...to copy the existing state before overwriting a field.- Think:
setUser({ ...user, name: newValue }).- Forget the
...user, and your new object will only containname, wiping out the rest!
4.2. Lab 2: Todo List “Pro” (Array State)
Manage a to-do list with basic CRUD operations on an Array.
- Create: Type text and press Enter to add a new task.
- Read: Display the list using
<ul>. - Delete: A “X” button next to each item to remove it.
- Update: (Optional Challenge) Click on the text to toggle a “Done/Undone” strikethrough.

💡 Pro Tip:
- Adding:
setTodos([...todos, newTask]).- Deleting: Use
.filter((item, index) => index !== indexToDelete).- Strictly Forbidden: Do not use
todos.push()ortodos.splice()directly on the state. Every operation must return a new array.
5. Key Takeaways
- One-way Data Flow: Data travels from parent to child.
- State vs. Variables: Use
useStatefor UI updates. Regular variables won’t cut it. - Immutability is Key:
- Objects: Use
...spread({ ...obj, key: value }). - Arrays: Use
...spread,.map(),.filter(). - NEVER:
.push(),.splice(), etc., directly on state.
- Objects: Use