> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 03. State & useState Hook

> Master component state management with the useState hook.

<img src="https://mintcdn.com/devweeekends/X0Fp4X8lMl-ZftoO/images/courses/react-crash-course/props-vs-state.svg?fit=max&auto=format&n=X0Fp4X8lMl-ZftoO&q=85&s=36606a0292c5295f46e3c511b1465823" alt="Props vs State" width="1080" height="1080" data-path="images/courses/react-crash-course/props-vs-state.svg" />

# State & useState Hook

**State** is what makes React components interactive. It's data that changes over time and triggers re-renders when updated.

Think of **state as a component's memory**. Just like you remember whether a light switch is on or off, a component remembers its own data -- the current count, whether a dropdown is open, the text a user has typed so far. When that memory changes, React re-renders the component so the screen stays in sync with the data.

By contrast, **props** are like arguments passed to a function -- they come from the outside and the component cannot change them. State is owned *by* the component; props are *given to* the component.

## State vs Props

Understanding the difference is crucial:

| Props                           | State                              |
| ------------------------------- | ---------------------------------- |
| Passed from parent              | Managed within component           |
| Read-only (immutable)           | Can be updated                     |
| Received as function parameters | Created with `useState`            |
| Changes come from parent        | Changes come from component itself |

```
┌─────────────────────────────────────────────────────────────────┐
│                        Parent Component                         │
│                                                                 │
│                    ┌─────────────────────┐                     │
│                    │  state = { count }  │                     │
│                    └─────────────────────┘                     │
│                              │                                  │
│                    passes as prop                               │
│                              ▼                                  │
│              ┌───────────────────────────────┐                 │
│              │      Child Component          │                 │
│              │   props = { count: 5 }        │                 │
│              │   (read-only)                 │                 │
│              └───────────────────────────────┘                 │
└─────────────────────────────────────────────────────────────────┘
```

## The `useState` Hook

Hooks are functions that let you "hook into" React features. `useState` is the fundamental hook for managing state.

### Basic Syntax

```javascript theme={null}
import { useState } from 'react';

const [stateValue, setStateFunction] = useState(initialValue);
```

* **stateValue**: Current value of the state
* **setStateFunction**: Function to update the state
* **initialValue**: Starting value (any type: string, number, boolean, object, array)

### Simple Counter Example

```javascript theme={null}
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}
```

### Toggle Example

```javascript theme={null}
function ToggleButton() {
  const [isOn, setIsOn] = useState(false);

  return (
    <button 
      onClick={() => setIsOn(!isOn)}
      style={{ backgroundColor: isOn ? 'green' : 'gray' }}
    >
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}
```

***

## Rules of useState

### 1. Never Modify State Directly

React won't know the state changed and won't re-render.

```javascript theme={null}
// ❌ WRONG - Direct mutation
count = count + 1;

// ✅ CORRECT - Use the setter function
setCount(count + 1);
```

### 2. Call Hooks at the Top Level

Don't call hooks inside loops, conditions, or nested functions.

```javascript theme={null}
function BadComponent() {
  // ❌ WRONG - Hook inside condition
  if (someCondition) {
    const [value, setValue] = useState(0);
  }
}

function GoodComponent() {
  // ✅ CORRECT - Hook at top level
  const [value, setValue] = useState(0);
  
  // Use conditions with the state value, not the hook
  if (someCondition) {
    // do something with value
  }
}
```

### 3. Only Call Hooks in React Functions

Hooks only work in React function components or custom hooks.

***

## Functional Updates (Previous State)

When your new state depends on the previous state, use the **functional update** form:

```javascript theme={null}
// ❌ Can cause bugs with multiple rapid updates
setCount(count + 1);
setCount(count + 1); // Both use the same stale 'count' value!

// ✅ CORRECT - Uses the latest state
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1); // Each gets the truly updated value
```

<Warning>
  **Why does this matter? (The Stale Closure Problem)** React batches state updates for performance. When you click a button that triggers multiple `setCount` calls, they all run in the same render cycle, so they all read the same "snapshot" of `count`. This is called a **stale closure** -- the function closes over an old value of the variable.

  The functional form `setCount(prev => prev + 1)` avoids this because React passes the *latest* state value to your updater function, not the stale one captured in the closure.

  This is one of the most common sources of bugs in React. If your state update depends on the current state, always use the functional form.
</Warning>

### Practical Example

```javascript theme={null}
function BatchingDemo() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    // All three will use functional updates for correct behavior
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    // Result: count increments by 3
  };

  return <button onClick={handleClick}>Add 3 (count: {count})</button>;
}
```

***

## Lazy Initialization

If your initial state requires expensive computation, pass a function:

```javascript theme={null}
// ❌ Expensive function runs on EVERY render
const [data, setData] = useState(expensiveComputation());

// ✅ Function only runs on initial render
const [data, setData] = useState(() => expensiveComputation());
```

### Real-World Example

```javascript theme={null}
function TodoApp() {
  // Load todos from localStorage only on first render
  const [todos, setTodos] = useState(() => {
    const saved = localStorage.getItem('todos');
    return saved ? JSON.parse(saved) : [];
  });

  // ...
}
```

***

## State with Objects

When state is an object, you must spread the previous state to avoid losing properties.

```javascript theme={null}
function UserForm() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    age: 0
  });

  const updateName = (newName) => {
    // ❌ WRONG - loses email and age
    setUser({ name: newName });
    
    // ✅ CORRECT - spread previous state
    setUser({ ...user, name: newName });
    
    // ✅ ALSO CORRECT - functional update
    setUser(prev => ({ ...prev, name: newName }));
  };

  return (
    <form>
      <input
        value={user.name}
        onChange={(e) => setUser({ ...user, name: e.target.value })}
        placeholder="Name"
      />
      <input
        value={user.email}
        onChange={(e) => setUser({ ...user, email: e.target.value })}
        placeholder="Email"
      />
    </form>
  );
}
```

### Nested Objects

For deeply nested objects, consider flattening your state or using a library like Immer.

```javascript theme={null}
const [user, setUser] = useState({
  name: 'Alice',
  address: {
    city: 'NYC',
    zip: '10001'
  }
});

// Updating nested property
setUser(prev => ({
  ...prev,
  address: {
    ...prev.address,
    city: 'LA'
  }
}));
```

***

## State with Arrays

Arrays require special handling because they're reference types.

```javascript theme={null}
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Learn React', done: false },
    { id: 2, text: 'Build project', done: false }
  ]);

  // ✅ Add item
  const addTodo = (text) => {
    setTodos([...todos, { id: Date.now(), text, done: false }]);
  };

  // ✅ Remove item
  const removeTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  // ✅ Update item
  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };

  // ✅ Insert at specific position
  const insertAt = (index, newTodo) => {
    setTodos([
      ...todos.slice(0, index),
      newTodo,
      ...todos.slice(index)
    ]);
  };

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.done}
            onChange={() => toggleTodo(todo.id)}
          />
          <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}
```

<Warning>
  **Never mutate arrays directly!** Methods like `push`, `pop`, `splice`, `sort`, and `reverse` mutate the original array.

  ```javascript theme={null}
  // ❌ WRONG - mutates original array
  todos.push(newTodo);
  setTodos(todos);

  // ✅ CORRECT - creates new array
  setTodos([...todos, newTodo]);
  ```
</Warning>

### Array Operation Cheat Sheet

| Operation | Mutating (Don't Use)     | Non-Mutating (Use This)            |
| --------- | ------------------------ | ---------------------------------- |
| Add       | `push`, `unshift`        | `[...arr, item]`, `[item, ...arr]` |
| Remove    | `pop`, `shift`, `splice` | `filter()`                         |
| Replace   | `arr[i] = x`, `splice`   | `map()`                            |
| Sort      | `sort()`                 | `[...arr].sort()`                  |
| Reverse   | `reverse()`              | `[...arr].reverse()`               |

***

## Multiple State Variables

You can (and often should) use multiple `useState` calls:

```javascript theme={null}
function UserProfile() {
  // Separate concerns into individual state pieces
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [isSubscribed, setIsSubscribed] = useState(false);
  const [notifications, setNotifications] = useState([]);
  
  // Each can be updated independently
}
```

### When to Use Object State vs Multiple States

```javascript theme={null}
// ✅ Multiple states - when values change independently
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');

// ✅ Object state - when values always change together
const [position, setPosition] = useState({ x: 0, y: 0 });

// ✅ Object state - for forms with many fields
const [formData, setFormData] = useState({
  username: '',
  email: '',
  password: '',
  confirmPassword: ''
});
```

***

## State Updates and Re-rendering

When state changes, React:

1. Schedules a re-render
2. Calls your component function again
3. Compares the new JSX with the previous
4. Updates only what changed in the DOM

```javascript theme={null}
function RenderDemo() {
  const [count, setCount] = useState(0);
  
  console.log('Component rendered!'); // Logs on every render

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
```

<Note>
  **State updates are asynchronous.** The new value isn't immediately available after calling the setter.

  ```javascript theme={null}
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // Still logs 0, not 1!
  };
  ```
</Note>

***

## 🎯 Practice Exercises

<Accordion title="Exercise 1: Shopping Cart Item Counter">
  Build a product card with quantity controls:

  ```javascript theme={null}
  function ProductCard({ name, price }) {
    const [quantity, setQuantity] = useState(0);

    const increment = () => setQuantity(prev => prev + 1);
    const decrement = () => setQuantity(prev => Math.max(0, prev - 1));

    return (
      <div className="product-card">
        <h3>{name}</h3>
        <p>${price}</p>
        <div className="quantity-controls">
          <button onClick={decrement} disabled={quantity === 0}>−</button>
          <span>{quantity}</span>
          <button onClick={increment}>+</button>
        </div>
        <p>Total: ${(price * quantity).toFixed(2)}</p>
      </div>
    );
  }
  ```
</Accordion>

<Accordion title="Exercise 2: Expandable FAQ">
  Build an FAQ item that expands/collapses:

  ```javascript theme={null}
  function FAQItem({ question, answer }) {
    const [isOpen, setIsOpen] = useState(false);

    return (
      <div className="faq-item">
        <button 
          onClick={() => setIsOpen(!isOpen)}
          className="faq-question"
        >
          {question}
          <span>{isOpen ? '−' : '+'}</span>
        </button>
        {isOpen && (
          <div className="faq-answer">
            {answer}
          </div>
        )}
      </div>
    );
  }

  // Usage
  <FAQItem 
    question="What is React?" 
    answer="React is a JavaScript library for building user interfaces."
  />
  ```
</Accordion>

<Accordion title="Exercise 3: Multi-Step Form">
  Build a form that tracks data across multiple steps:

  ```javascript theme={null}
  function MultiStepForm() {
    const [step, setStep] = useState(1);
    const [formData, setFormData] = useState({
      name: '',
      email: '',
      plan: 'basic'
    });

    const updateField = (field, value) => {
      setFormData(prev => ({ ...prev, [field]: value }));
    };

    const nextStep = () => setStep(prev => Math.min(prev + 1, 3));
    const prevStep = () => setStep(prev => Math.max(prev - 1, 1));

    return (
      <div className="multi-step-form">
        <div className="progress">Step {step} of 3</div>
        
        {step === 1 && (
          <div>
            <h2>Personal Info</h2>
            <input
              value={formData.name}
              onChange={(e) => updateField('name', e.target.value)}
              placeholder="Your name"
            />
          </div>
        )}
        
        {step === 2 && (
          <div>
            <h2>Contact</h2>
            <input
              type="email"
              value={formData.email}
              onChange={(e) => updateField('email', e.target.value)}
              placeholder="Your email"
            />
          </div>
        )}
        
        {step === 3 && (
          <div>
            <h2>Review</h2>
            <p>Name: {formData.name}</p>
            <p>Email: {formData.email}</p>
            <p>Plan: {formData.plan}</p>
          </div>
        )}
        
        <div className="buttons">
          {step > 1 && <button onClick={prevStep}>Back</button>}
          {step < 3 ? (
            <button onClick={nextStep}>Next</button>
          ) : (
            <button onClick={() => alert('Submitted!')}>Submit</button>
          )}
        </div>
      </div>
    );
  }
  ```
</Accordion>

***

## Summary

| Concept             | Description                                                      |
| ------------------- | ---------------------------------------------------------------- |
| `useState`          | Hook to add state to functional components                       |
| Setter Function     | Only way to update state and trigger re-render                   |
| Functional Updates  | `setState(prev => newValue)` for updates based on previous state |
| Lazy Initialization | `useState(() => expensiveCalc())` for expensive initial values   |
| Immutability        | Never mutate state directly; always create new values            |
| Objects             | Spread previous state: `{ ...prev, newProp }`                    |
| Arrays              | Use `map`, `filter`, spread; never `push`, `pop`, `splice`       |
| Batching            | React batches multiple state updates for performance             |

<Card title="Next Steps" icon="arrow-right">
  In the next chapter, you'll learn about **Handling Events** — responding to user clicks, form submissions, and keyboard input!
</Card>

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="Explain React's state batching behavior. How did it change in React 18, and what are the practical implications?">
    **Strong Answer:**
    State batching means React groups multiple `setState` calls into a single re-render instead of re-rendering after each one. Before React 18, batching only happened inside React event handlers. If you called `setCount(1); setName('Alice')` inside an `onClick`, React batched them into one render. But if those same calls happened inside a `setTimeout`, a Promise `.then()`, or a native event listener, each `setState` triggered a separate re-render -- two renders instead of one.

    React 18 introduced automatic batching everywhere: event handlers, timeouts, promises, native event listeners, even `queueMicrotask`. This was a significant performance improvement for real-world apps. I worked on a dashboard that fetched data in a `useEffect` and then called three separate state setters (`setData`, `setLoading`, `setError`). Before React 18, that caused three re-renders in sequence. After upgrading, it became one render.

    The practical implication is that you should not expect state to be updated synchronously between consecutive `setState` calls. If you need to read the "latest" state after an update, use the functional updater: `setState(prev => prev + 1)`. If you genuinely need to force a synchronous flush (rare), React 18 provides `flushSync` from `react-dom`, which opts out of batching for that specific update.

    **Follow-up: What is the stale closure problem, and why does the functional updater pattern solve it? Give a concrete scenario.**

    A stale closure happens when a function captures a variable from its surrounding scope, but by the time the function executes, that variable holds an old value. In React, this is extremely common with `useEffect` and event handlers.

    Concrete scenario: a counter component with a "increment 3 times" button that does `setCount(count + 1)` three times in a row. All three calls read the same `count` value from the closure (say, 0), so all three compute `0 + 1 = 1`. The result is the count goes to 1, not 3.

    The functional updater `setCount(prev => prev + 1)` fixes this because React does not read from the closure -- it passes the truly current state value as the `prev` argument to each updater function in sequence. First call: `prev` is 0, returns 1. Second call: `prev` is 1, returns 2. Third call: `prev` is 2, returns 3. The final state is 3.

    The deeper reason this works is that React queues updater functions internally and processes them in order during the next render, each receiving the output of the previous one. It is essentially a reduce operation over the queue of updaters.
  </Accordion>

  <Accordion title="When should you use a single state object versus multiple useState calls? What are the tradeoffs?">
    **Strong Answer:**
    The key question is: do these values change together or independently?

    Multiple `useState` calls are better when values are independent. A form with a name field and a "terms accepted" checkbox -- those change at different times for different reasons. Separate state makes each updater simpler, avoids unnecessary spreads, and makes it obvious which state a setter modifies.

    A single state object is better when values always change together. Mouse coordinates `{ x, y }` are a good example -- you never update `x` without `y`. A form with many fields that share a single `handleChange` handler is another: a single state object with computed property names `[name]: value` is cleaner than 15 separate `useState` calls.

    The tradeoff with object state: you must spread the previous state on every update (`{ ...prev, name: newName }`), which is verbose and error-prone. Forget the spread and you lose properties. For deeply nested state (like `user.address.city`), the spread nesting becomes painful: `{ ...state, address: { ...state.address, city: 'LA' } }`. At that point, I reach for `useReducer` or a library like Immer.

    The tradeoff with many separate states: if you have 10+ `useState` calls, the component becomes cluttered and it is hard to see which states are related. This is a signal to either consolidate into an object, extract into a custom hook, or use `useReducer`.

    My rule of thumb: 1-4 independent values, use separate `useState`. 5+ related values or complex update logic, use `useReducer`. Object state with `useState` is the middle ground I use for forms with 3-8 fields.

    **Follow-up: You mentioned Immer. How does Immer work, and why does Redux Toolkit use it by default?**

    Immer uses JavaScript Proxies to create a "draft" copy of your state. You write code that looks like direct mutation (`draft.user.name = 'Alice'`), but Immer intercepts every property access and mutation through the Proxy, tracks what changed, and produces a new immutable object with structural sharing -- only the changed parts are new objects; unchanged branches keep the same references.

    Redux Toolkit uses Immer in `createSlice` because reducer boilerplate was the number one complaint about Redux. Writing `return { ...state, users: { ...state.users, [id]: { ...state.users[id], name: 'Alice' } } }` for a nested update is painful and error-prone. With Immer, you write `state.users[id].name = 'Alice'` and get the same immutable result. The code is dramatically easier to read and less likely to have spread-related bugs.

    The tradeoff: Immer adds bundle size (around 6KB gzipped) and a small runtime overhead from Proxy interception. For most apps this is negligible. The one gotcha is that Immer-wrapped code looks mutable, so developers new to the codebase may not realize immutability is still being enforced. If they write the same "mutation" style outside of a `createSlice` reducer, they will create actual mutations and introduce bugs.
  </Accordion>

  <Accordion title="What is lazy initialization in useState, and when have you needed it in production?">
    **Strong Answer:**
    Lazy initialization is passing a function to `useState` instead of a value: `useState(() => expensiveComputation())` instead of `useState(expensiveComputation())`. The difference is that the function form only executes once on the initial render, while the value form executes on every render even though React discards the result after the first.

    This matters when the initial value requires expensive work. The most common production example I have used is reading from `localStorage`: `useState(() => JSON.parse(localStorage.getItem('settings') || '{}'))`. Without lazy init, `JSON.parse` and the `localStorage.getItem` call happen on every re-render. For a component that re-renders frequently (typing in a search field, for example), that is wasted work. `localStorage` access is synchronous and hits disk I/O, and `JSON.parse` on a large settings object adds CPU overhead.

    Another case: generating initial data structures. If your initial state is a large Map or a sorted array, computing it on every render wastes resources. `useState(() => buildInitialMap(rawData))` ensures it runs once.

    The gotcha: lazy init only helps with the initial computation. If you need to recompute state when a prop changes, lazy init does not help -- you need `useEffect` or a key reset. Also, the initializer function receives no arguments, so you cannot pass parameters to it directly (use a closure instead).

    **Follow-up: You have a component that reads from localStorage on mount. A user opens two tabs. How do you keep them in sync?**

    localStorage does not automatically sync state across tabs within your React app. One tab writes, the other tab's React state is stale until a full page refresh. The solution is the `storage` event, which fires on other tabs (not the originating tab) when localStorage changes.

    You set up a `useEffect` that listens for the `storage` event on the window: `window.addEventListener('storage', handler)`. In the handler, you check `event.key` to see if it matches your key, and if so, update the React state with `event.newValue`. You also need cleanup in the useEffect return.

    The edge case: the `storage` event only fires on *other* tabs, not the one that wrote the value. If you also need to react to changes within the same tab (from a different component), you need a custom event or a shared Context/store. Libraries like `usehooks-ts` provide a `useLocalStorage` hook that handles cross-tab sync out of the box.
  </Accordion>

  <Accordion title="Why does React require immutable state updates? What would break if you mutated state directly?">
    **Strong Answer:**
    React uses reference equality (triple equals) to determine whether state has changed. When you call `setState`, React compares the new value with the old value. For objects and arrays, this comparison checks whether they are the same reference in memory, not whether their contents are different.

    If you mutate an object and pass the same reference back -- `state.items.push(newItem); setState(state.items)` -- React sees the same reference and concludes nothing changed. It skips the re-render entirely. Your data is updated in memory, but the UI is stale. This is the most immediate and visible breakage.

    But there are deeper problems. `React.memo` relies on shallow prop comparison. If a memoized child receives the same object reference, it skips rendering even though the object's internals changed. `useMemo` and `useCallback` dependency checks also use reference equality -- stale dependencies mean stale memoized values.

    The React DevTools time-travel debugger relies on each state update producing a distinct snapshot. If you mutate in place, previous "snapshots" are also mutated because they all point to the same object. You lose the ability to inspect or replay past states.

    Concurrent features in React 18+ rely on being able to throw away a partial render and restart it. If reducers mutate state, a discarded render may have already altered the "real" state, causing corruption.

    In my experience, mutation bugs are insidious because they manifest inconsistently. They might work in development (where batching and timing differ) but fail in production. They might work 99 times and fail on the 100th due to a race between renders.

    **Follow-up: How do you enforce immutability in a team codebase? What tools or patterns help?**

    Three layers of defense. First, TypeScript with `Readonly<T>` and `ReadonlyArray<T>` types on state. This catches mutations at compile time. Second, ESLint with the `eslint-plugin-react` rule `no-direct-mutation-state` and the Immer-based patterns in Redux Toolkit that make "mutation" safe by design. Third, `Object.freeze()` in development (React already does this for props) -- though manually freezing state is usually overkill.

    For team adoption, the most effective approach is establishing the pattern of always using the functional updater form (`setState(prev => ({ ...prev, field: newValue }))`) and reviewing PRs for `.push()`, `.splice()`, direct property assignment, and `.sort()` without a spread. A lint rule that forbids array mutation methods on state variables catches most issues automatically.
  </Accordion>
</AccordionGroup>
