# useState Hook in React [part-3]

State updates seem straightforward until they're not. Asynchronous operations, rapid user interactions, and event handlers create scenarios where your state updates reference stale values. Functional updates solve this elegantly, ensuring your state transformations always work with current data.

Understanding this small things can help in optimizing the code and creating bug less features. Everything matters at scale.

## The Stale State Problem

Consider this seemingly innocent counter:

```javascript
function BrokenCounter() {
  const [count, setCount] = useState(0);

  const incrementThreeTimes = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementThreeTimes}>Add 3</button>
    </div>
  );
}
```

You expect the count to increase by 3. Instead, it increases by 1. Why? All three `setCount` calls capture the same `count` value from the render when the button was clicked. Each reads `count` as 0, resulting in three calls of `setCount(0 + 1)`.

## Functional Updates: The Solution

Pass a function to your state setter instead of a value. React calls this function with the current state, guaranteeing you always work with up-to-date data:

```javascript
function ReliableCounter() {
  const [count, setCount] = useState(0);

  const incrementThreeTimes = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementThreeTimes}>Add 3</button>
    </div>
  );
}
```

Now each update receives the result of the previous update. The first call gets 0 and returns 1. The second call gets 1 and returns 2. The third call gets 2 and returns 3. Perfect.

---

## Real-World Example: Shopping Cart

```javascript
import { useState } from 'react';

function ShoppingCart() {
  const [items, setItems] = useState([]);

  const addItem = (product) => {
    setItems(prevItems => {
      const existingItem = prevItems.find(item => item.id === product.id);
      
      if (existingItem) {
        // Increase quantity if item exists
        return prevItems.map(item =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      }
      
      // Add new item
      return [...prevItems, { ...product, quantity: 1 }];
    });
  };

  const removeItem = (productId) => {
    setItems(prevItems => prevItems.filter(item => item.id !== productId));
  };

  const updateQuantity = (productId, newQuantity) => {
    if (newQuantity <= 0) {
      removeItem(productId);
      return;
    }
    
    setItems(prevItems =>
      prevItems.map(item =>
        item.id === productId
          ? { ...item, quantity: newQuantity }
          : item
      )
    );
  };

  const getTotalPrice = () => {
    return items.reduce((total, item) => total + (item.price * item.quantity), 0);
  };

  return (
    <div className="shopping-cart">
      <h2>Shopping Cart</h2>
      {items.length === 0 ? (
        <p>Your cart is empty</p>
      ) : (
        <>
          <ul>
            {items.map(item => (
              <li key={item.id}>
                <span>{item.name}</span>
                <span>${item.price}</span>
                <input
                  type="number"
                  value={item.quantity}
                  onChange={(e) => updateQuantity(item.id, parseInt(e.target.value))}
                  min="0"
                />
                <button onClick={() => removeItem(item.id)}>Remove</button>
              </li>
            ))}
          </ul>
          <div className="cart-total">
            <strong>Total: ${getTotalPrice().toFixed(2)}</strong>
          </div>
        </>
      )}
    </div>
  );
}
```

This cart uses functional updates throughout. The `addItem` function reads the previous items array to check if a product exists. Without functional updates, rapidly clicking "Add to Cart" could result in duplicate entries instead of increased quantities.

---

## Asynchronous Operations and Functional Updates

```javascript
function AsyncCounter() {
  const [count, setCount] = useState(0);
  const [loading, setLoading] = useState(false);

  const incrementAfterDelay = async () => {
    setLoading(true);
    
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // Using functional update ensures we increment the current count
    // even if the user triggered multiple async operations
    setCount(prevCount => prevCount + 1);
    setLoading(false);
  };

  const incrementMultipleTimes = () => {
    // Start 3 async operations simultaneously
    incrementAfterDelay();
    incrementAfterDelay();
    incrementAfterDelay();
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementMultipleTimes} disabled={loading}>
        Increment 3 Times (Async)
      </button>
      {loading && <p>Loading...</p>}
    </div>
  );
}
```

Without functional updates, all three async operations would capture the initial `count` value, resulting in the counter only incrementing by 1 regardless of how many operations complete.

---

## Production Pattern: Todo List with Optimistic Updates

```javascript
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const addTodo = async () => {
    if (!inputValue.trim()) return;

    const tempId = Date.now();
    const newTodo = {
      id: tempId,
      text: inputValue,
      completed: false,
      syncing: true
    };

    // Optimistic update
    setTodos(prevTodos => [...prevTodos, newTodo]);
    setInputValue('');

    try {
      // Simulate API call
      const response = await fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify({ text: newTodo.text }),
        headers: { 'Content-Type': 'application/json' }
      });
      
      const savedTodo = await response.json();

      // Update with real ID from server
      setTodos(prevTodos =>
        prevTodos.map(todo =>
          todo.id === tempId
            ? { ...savedTodo, syncing: false }
            : todo
        )
      );
    } catch (error) {
      // Rollback on error
      setTodos(prevTodos => prevTodos.filter(todo => todo.id !== tempId));
      alert('Failed to add todo');
    }
  };

  const toggleTodo = async (id) => {
    // Optimistic update
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id
          ? { ...todo, completed: !todo.completed, syncing: true }
          : todo
      )
    );

    try {
      await fetch(`/api/todos/${id}`, {
        method: 'PATCH',
        body: JSON.stringify({ completed: true }),
        headers: { 'Content-Type': 'application/json' }
      });

      // Remove syncing flag
      setTodos(prevTodos =>
        prevTodos.map(todo =>
          todo.id === id
            ? { ...todo, syncing: false }
            : todo
        )
      );
    } catch (error) {
      // Rollback on error
      setTodos(prevTodos =>
        prevTodos.map(todo =>
          todo.id === id
            ? { ...todo, completed: !todo.completed, syncing: false }
            : todo
        )
      );
    }
  };

  const deleteTodo = async (id) => {
    const todoToDelete = todos.find(todo => todo.id === id);
    
    // Optimistic delete
    setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));

    try {
      await fetch(`/api/todos/${id}`, { method: 'DELETE' });
    } catch (error) {
      // Rollback: add the todo back
      setTodos(prevTodos => [...prevTodos, todoToDelete]);
      alert('Failed to delete todo');
    }
  };

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && addTodo()}
        placeholder="Add a todo..."
      />
      <button onClick={addTodo}>Add</button>
      
      <ul>
        {todos.map(todo => (
          <li key={todo.id} style={{ opacity: todo.syncing ? 0.5 : 1 }}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
              disabled={todo.syncing}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)} disabled={todo.syncing}>
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}
```

This code implements a React functional component for a **To-Do List** using a technique called **Optimistic UI Updates**. This pattern makes the app feel faster by updating the interface immediately before waiting for a server response.

### Key Functionalities

* **Optimistic UI Strategy**: For every action (add, toggle, delete), the app updates the local state **first** so the user sees the change instantly. It then makes the API call in the background and corrects the state only if the server fails.
    
* **Syncing State**: While a network request is in progress, the code sets a `syncing` flag. This is used to:
    
    * **Visually dim** the item (setting `opacity: 0.5`).
        
    * **Disable interactions** (like clicking the checkbox or delete button) to prevent "race conditions" while the server is processing.
        
* **ID Management**:
    
    * When adding a task, it uses a **temporary ID** ([`Date.now`](http://Date.now)`()`) to render the item immediately.
        
    * Once the server responds, it replaces the temporary ID with the **permanent ID** returned by the database.
        
* **Error Handling & Rollbacks**:
    
    * If an API call fails, the `catch` blocks contain logic to **revert** the UI.
        
    * For example, if deleting fails, the item is added back to the list; if toggling fails, the checkbox switches back to its previous state.
        

With functional update this code works with integrity and consistency.

---

## When to use Functional Updates

### When Functional Updates Are Critical

**Multiple Updates in Single Event**: Any time you update state multiple times in one function.

**Async Operations**: setTimeout, setInterval, fetch calls, or any promise-based code.

**Event Handlers with Captured State**: Callbacks that close over state values.

**Computed Updates**: When the new state depends on the old state mathematically or logically.

### When Direct Updates Work Fine

**Setting Absolute Values**: When the new state doesn't depend on the old state:

```javascript
const resetCount = () => setCount(0); // Direct update is fine
const setToValue = (value) => setCount(value); // Also fine
```

**Single Update Per Event**: If you only update state once per event and don't have async operations:

```javascript
const handleClick = () => {
  setCount(count + 1); // Generally safe in isolation
};
```

### Performance Characteristics

Functional updates don't add meaningful overhead. React must process the update regardless. The function call itself is **negligible** compared to the re-render cost.

React batches multiple state updates automatically in React 18+, whether using direct or functional updates. Batching optimizes renders, not individual state setter calls.

---

## Common Patterns

**Toggle Boolean State**

```javascript
const [isOpen, setIsOpen] = useState(false);

// Instead of: setIsOpen(!isOpen)
const toggle = () => setIsOpen(prev => !prev);
```

**Increment/Decrement**

```javascript
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const incrementBy = (amount) => setCount(prev => prev + amount);
```

**Array Operations**

```javascript
// Add item
setItems(prev => [...prev, newItem]);

// Remove item
setItems(prev => prev.filter(item => item.id !== idToRemove));

// Update item
setItems(prev => prev.map(item =>
  item.id === idToUpdate ? { ...item, ...updates } : item
));

// Sort
setItems(prev => [...prev].sort((a, b) => a.value - b.value));
```

**Object Updates**

```javascript
// Merge properties
setUser(prev => ({ ...prev, name: 'John' }));

// Nested update
setSettings(prev => ({
  ...prev,
  preferences: {
    ...prev.preferences,
    theme: 'dark'
  }
}));
```

---

## TypeScript Integration

```javascript
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

function TypedTodoList() {
  const [todos, setTodos] = useState<Todo[]>([]);

  const addTodo = (text: string) => {
    setTodos((prevTodos: Todo[]) => [
      ...prevTodos,
      { id: Date.now(), text, completed: false }
    ]);
  };

  return <div>{/* UI */}</div>;
}
```

TypeScript infers the parameter type automatically in most cases, but explicit typing clarifies intent.

---

## Key Notes

<mark>Use functional updates when new state depends on previous state.</mark> This pattern prevents bugs in async operations, multiple updates, and event handlers with captured state.

The pattern is simple: pass a function instead of a value. React guarantees your function receives the current state, eliminating race conditions and stale closures.

Direct updates work perfectly when setting absolute values or making single updates without dependencies on previous state.
