Skip to Content
Module 3: State & Interactive UI3.1 useState Deep Dive

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

Banner

🎯 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 Object or Array state. We’ll help you avoid the common mutating state pitfalls.

1. One-way Data Flow

React orchestrates data flow in one single direction: from Parent components down to Child components, passed via Props.

One-way Data Flow

  • 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.

State vs Variable

Here’s the Catch:

  1. 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.
  2. 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.

Immutability

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.

User Profile

💡 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 contain name, 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.

Task List

💡 Pro Tip:

  • Adding: setTodos([...todos, newTask]).
  • Deleting: Use .filter((item, index) => index !== indexToDelete).
  • Strictly Forbidden: Do not use todos.push() or todos.splice() directly on the state. Every operation must return a new array.

5. Key Takeaways

  1. One-way Data Flow: Data travels from parent to child.
  2. State vs. Variables: Use useState for UI updates. Regular variables won’t cut it.
  3. Immutability is Key:
    • Objects: Use ...spread ({ ...obj, key: value }).
    • Arrays: Use ...spread, .map(), .filter().
    • NEVER: .push(), .splice(), etc., directly on state.

6. Further Reading

Last updated on