# State & Interactivity

In my POS system, every product card needed an "Add to Cart" button with a quantity selector. Users should be able to click "+" to increase quantity, "-" to decrease it, and see the count update instantly. This sounds simple, but it taught me one of React's most fundamental concepts: **state**.

A static component that just displays data isn't enough. Real applications need components that respond to user actions—clicks, typing, selections. That's where state comes in.

## What Is State?

**State is data that changes over time.** When a component's state changes, React automatically re-renders it with the new data.

In the POS system:

* The quantity in a cart item is **state** (it changes when users click +/-)
* The product name is **not state** (it doesn't change)
* Whether a modal is open is **state** (it toggles on/off)
* The product price is **not state** (it's fixed data from the API)

**The rule**: If it changes in response to user interaction or time, it's state.

## Your First State: A Counter

Let's build the quantity selector from the POS system—a simple counter with +/- buttons.

### Without State (Broken Version)

First, let's see what doesn't work:

```typescript
// QuantityCounter.tsx
import React from 'react';

function QuantityCounter() {
  let quantity = 0; // ❌ This won't work!
  
  const increment = () => {
    quantity = quantity + 1;
    console.log(quantity); // This will log the value
  };
  
  const decrement = () => {
    quantity = quantity - 1;
    console.log(quantity);
  };
  
  return (
    <div className="quantity-counter">
      <button onClick={decrement}>-</button>
      <span>{quantity}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

export default QuantityCounter;
```

**What happens?**

* You click the buttons
* The console shows the numbers changing
* But the UI shows "0" forever

**Why?** React doesn't know `quantity` changed. Regular variables don't trigger re-renders.

### With State (Working Version)

```typescript
// QuantityCounter.tsx
import React, { useState } from 'react';

function QuantityCounter() {
  const [quantity, setQuantity] = useState(0); // ✅ This works!
  
  const increment = () => {
    setQuantity(quantity + 1);
  };
  
  const decrement = () => {
    setQuantity(quantity - 1);
  };
  
  return (
    <div className="quantity-counter">
      <button onClick={decrement}>-</button>
      <span>{quantity}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

export default QuantityCounter;
```

**Now it works!** Click +, the number increases. Click -, it decreases.

## Understanding useState

```typescript
const [quantity, setQuantity] = useState(0);
```

Let's break this down:

1. **`useState`** is a React Hook (we'll explain Hooks more later)
2. **`0`** is the initial value of the state
3. **`quantity`** is the current state value
4. **`setQuantity`** is a function to update the state
5. **Array destructuring** extracts both values from `useState`

**Every time you call `setQuantity(newValue)`:**

1. React updates the state to `newValue`
2. React re-renders the component
3. The component function runs again with the new state value
4. The UI updates to show the new value

## Event Handlers

```typescript
<button onClick={increment}>+</button>
```

**`onClick`** is an event handler. React supports all standard DOM events:

* `onClick` - User clicks
* `onChange` - Input value changes
* `onSubmit` - Form submitted
* `onMouseEnter` - Mouse hovers
* `onKeyDown` - Key pressed
* And many more

**Important**: Pass the **function reference**, not a function call:

```typescript
<button onClick={increment}>+</button>      // ✅ Correct
<button onClick={increment()}>+</button>    // ❌ Calls immediately
<button onClick={() => increment()}>+</button> // ✅ Also correct (arrow function)
```

## Real Example: Add to Cart Button

In the POS system, each product card has an "Add to Cart" button. Let's build a more realistic version:

```typescript
// AddToCartButton.tsx
import React, { useState } from 'react';

interface AddToCartButtonProps {
  productName: string;
  maxStock: number;
}

function AddToCartButton({ productName, maxStock }: AddToCartButtonProps) {
  const [quantity, setQuantity] = useState(0);
  
  const increment = () => {
    if (quantity < maxStock) {
      setQuantity(quantity + 1);
    }
  };
  
  const decrement = () => {
    if (quantity > 0) {
      setQuantity(quantity - 1);
    }
  };
  
  const addToCart = () => {
    if (quantity > 0) {
      console.log(`Added ${quantity}x ${productName} to cart`);
      // In real app: dispatch to cart context/state
      setQuantity(0); // Reset after adding
    }
  };
  
  return (
    <div className="add-to-cart">
      <div className="quantity-selector">
        <button 
          onClick={decrement} 
          disabled={quantity === 0}
          className="btn-quantity"
        >
          -
        </button>
        
        <span className="quantity-display">{quantity}</span>
        
        <button 
          onClick={increment} 
          disabled={quantity === maxStock}
          className="btn-quantity"
        >
          +
        </button>
      </div>
      
      <button 
        onClick={addToCart} 
        disabled={quantity === 0}
        className="btn-add-to-cart"
      >
        Add to Cart
      </button>
    </div>
  );
}

export default AddToCartButton;
```

**New concepts here:**

1. **Conditional logic in handlers**: Check `quantity < maxStock` before incrementing
2. **Disabled buttons**: `disabled={quantity === 0}` prevents clicks when invalid
3. **Props for configuration**: `maxStock` comes from the product's available inventory
4. **State reset**: After adding to cart, reset quantity to 0

## Multiple State Variables

Components can have multiple pieces of state. Here's a product card with quantity **and** a "favorited" toggle:

```typescript
// ProductCard.tsx
import React, { useState } from 'react';

interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
  imageUrl?: string;
}

interface ProductCardProps {
  product: Product;
}

function ProductCard({ product }: ProductCardProps) {
  const [quantity, setQuantity] = useState(0);
  const [isFavorited, setIsFavorited] = useState(false);
  
  const toggleFavorite = () => {
    setIsFavorited(!isFavorited);
  };
  
  const increment = () => {
    if (quantity < product.stock) {
      setQuantity(quantity + 1);
    }
  };
  
  const decrement = () => {
    if (quantity > 0) {
      setQuantity(quantity - 1);
    }
  };
  
  return (
    <div className="product-card">
      <button 
        onClick={toggleFavorite} 
        className="favorite-btn"
      >
        {isFavorited ? '❤️' : '🤍'}
      </button>
      
      {product.imageUrl && (
        <img src={product.imageUrl} alt={product.name} />
      )}
      
      <h3>{product.name}</h3>
      <p className="price">{product.price.toLocaleString()} MMK</p>
      <p className="stock">Available: {product.stock}</p>
      
      <div className="quantity-selector">
        <button onClick={decrement} disabled={quantity === 0}>-</button>
        <span>{quantity}</span>
        <button onClick={increment} disabled={quantity === product.stock}>+</button>
      </div>
      
      <button 
        disabled={quantity === 0}
        className="add-to-cart-btn"
      >
        Add {quantity > 0 && `${quantity}x`} to Cart
      </button>
    </div>
  );
}

export default ProductCard;
```

**Each state variable is independent:**

* Changing `quantity` doesn't affect `isFavorited`
* Each has its own setter function
* Both cause re-renders when changed

## State with Objects

Sometimes state is more complex than a single number. In the POS system, I tracked cart items as objects:

```typescript
// CartItem.tsx
import React, { useState } from 'react';

interface CartItem {
  productId: string;
  productName: string;
  quantity: number;
  price: number;
}

function CartItemDisplay() {
  const [item, setItem] = useState<CartItem>({
    productId: 'prod-1',
    productName: 'Mohinga',
    quantity: 1,
    price: 3500
  });
  
  const incrementQuantity = () => {
    setItem({
      ...item,                    // ← Spread existing properties
      quantity: item.quantity + 1 // ← Override just quantity
    });
  };
  
  const decrementQuantity = () => {
    setItem({
      ...item,
      quantity: item.quantity - 1
    });
  };
  
  const total = item.quantity * item.price;
  
  return (
    <div className="cart-item">
      <h4>{item.productName}</h4>
      <p>Price: {item.price} MMK</p>
      
      <div className="quantity-controls">
        <button onClick={decrementQuantity} disabled={item.quantity === 1}>
          -
        </button>
        <span>{item.quantity}</span>
        <button onClick={incrementQuantity}>+</button>
      </div>
      
      <p className="total">Total: {total.toLocaleString()} MMK</p>
    </div>
  );
}

export default CartItemDisplay;
```

**Critical**: When updating object state, **always create a new object**:

```typescript
// ❌ Wrong - Mutates existing state
item.quantity = item.quantity + 1;
setItem(item);

// ✅ Correct - Creates new object
setItem({
  ...item,
  quantity: item.quantity + 1
});
```

The spread operator `...item` copies all existing properties, then we override specific ones.

## Functional State Updates

There's a gotcha with state updates. When the new state depends on the old state, use the functional form:

```typescript
// ❌ Can cause bugs in certain scenarios
const increment = () => {
  setQuantity(quantity + 1);
};

// ✅ Always safe
const increment = () => {
  setQuantity(prevQuantity => prevQuantity + 1);
};
```

**Why?** State updates can be batched. If you click the button rapidly, the first version might miss some clicks. The functional form guarantees you're working with the most recent value.

**Real example from the POS:**

```typescript
const addMultipleItems = () => {
  // If user clicks "Add 5 items" button
  setQuantity(prev => prev + 1);
  setQuantity(prev => prev + 1);
  setQuantity(prev => prev + 1);
  setQuantity(prev => prev + 1);
  setQuantity(prev => prev + 1);
  // ✅ All updates apply correctly
};
```

## Event Object and Parameters

Sometimes you need information from the event:

```typescript
// Input field tracking what user types
function ProductSearch() {
  const [searchTerm, setSearchTerm] = useState('');
  
  const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSearchTerm(event.target.value);
  };
  
  return (
    <div>
      <input 
        type="text" 
        value={searchTerm}
        onChange={handleSearch}
        placeholder="Search products..."
      />
      <p>Searching for: {searchTerm}</p>
    </div>
  );
}
```

**Or with inline arrow function:**

```typescript
<input 
  type="text" 
  value={searchTerm}
  onChange={(e) => setSearchTerm(e.target.value)}
  placeholder="Search products..."
/>
```

Both work. The inline version is more concise for simple updates.

## Complete Example: POS Product Card with All Features

Here's a production-ready product card from the POS system combining everything we've learned:

```typescript
// ProductCard.tsx
import React, { useState } from 'react';
import styles from './ProductCard.module.css';

interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
  category: string;
  imageUrl?: string;
}

interface ProductCardProps {
  product: Product;
  onAddToCart: (productId: string, quantity: number) => void;
}

function ProductCard({ product, onAddToCart }: ProductCardProps) {
  const [quantity, setQuantity] = useState(0);
  const [isAdding, setIsAdding] = useState(false);
  
  const increment = () => {
    setQuantity(prev => Math.min(prev + 1, product.stock));
  };
  
  const decrement = () => {
    setQuantity(prev => Math.max(prev - 1, 0));
  };
  
  const handleAddToCart = () => {
    if (quantity > 0) {
      setIsAdding(true);
      onAddToCart(product.id, quantity);
      
      // Simulate API call delay
      setTimeout(() => {
        setIsAdding(false);
        setQuantity(0);
      }, 500);
    }
  };
  
  const isOutOfStock = product.stock === 0;
  const isLowStock = product.stock > 0 && product.stock < 5;
  const total = quantity * product.price;
  
  return (
    <div className={`${styles.card} ${isOutOfStock ? styles.outOfStock : ''}`}>
      {product.imageUrl && (
        <img src={product.imageUrl} alt={product.name} className={styles.image} />
      )}
      
      <div className={styles.content}>
        <h3>{product.name}</h3>
        <p className={styles.price}>{product.price.toLocaleString()} MMK</p>
        <span className={styles.category}>{product.category}</span>
        
        {isOutOfStock && (
          <p className={styles.outOfStockText}>Out of Stock</p>
        )}
        
        {isLowStock && (
          <p className={styles.lowStockWarning}>⚠️ Only {product.stock} left!</p>
        )}
        
        {!isOutOfStock && (
          <div className={styles.actions}>
            <div className={styles.quantitySelector}>
              <button 
                onClick={decrement} 
                disabled={quantity === 0}
                className={styles.btnQuantity}
              >
                -
              </button>
              
              <span className={styles.quantity}>{quantity}</span>
              
              <button 
                onClick={increment} 
                disabled={quantity === product.stock}
                className={styles.btnQuantity}
              >
                +
              </button>
            </div>
            
            {quantity > 0 && (
              <p className={styles.subtotal}>
                Subtotal: {total.toLocaleString()} MMK
              </p>
            )}
            
            <button 
              onClick={handleAddToCart}
              disabled={quantity === 0 || isAdding}
              className={styles.btnAddToCart}
            >
              {isAdding ? 'Adding...' : `Add to Cart ${quantity > 0 ? `(${quantity})` : ''}`}
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

export default ProductCard;
```

**Advanced patterns used:**

1. **Calculated values**: `total`, `isOutOfStock`, `isLowStock` are derived from state/props
2. **Conditional rendering**: Different UI based on stock status
3. **Disabled states**: Buttons disabled when appropriate
4. **Loading state**: `isAdding` shows feedback during async operations
5. **Math.min/Math.max**: Cleaner boundary checking
6. **Functional updates**: `setQuantity(prev => ...)` for reliability
7. **Callbacks to parent**: `onAddToCart` will be explained in Component Communication article

## Key Learnings

### ✅ State vs Props

* **Props**: Data passed from parent (read-only in child)
* **State**: Data managed by the component (can change)

### ✅ useState Pattern

```typescript
const [value, setValue] = useState(initialValue);
```

* Returns current value and setter function
* Calling setter triggers re-render
* Always use the setter, never mutate directly

### ✅ Event Handlers

* Pass function reference: `onClick={handleClick}`
* Or inline arrow: `onClick={() => handleClick()}`
* Access event object when needed: `onChange={(e) => ...}`

### ✅ State Updates

* For simple values: `setValue(newValue)`
* For dependent updates: `setValue(prev => prev + 1)`
* For objects: `setValue({ ...value, property: newValue })`

### ✅ Multiple State Variables

* Each component can have multiple `useState` calls
* Each is independent
* Name them descriptively

## Common Mistakes I Made

**❌ Mutating state directly**

```typescript
quantity = quantity + 1; // ❌ Won't trigger re-render
setQuantity(quantity + 1); // ✅ Correct
```

**❌ Calling function instead of passing reference**

```typescript
<button onClick={increment()}>+</button> // ❌ Calls immediately
<button onClick={increment}>+</button>   // ✅ Correct
```

**❌ Mutating object state**

```typescript
item.quantity = item.quantity + 1;
setItem(item); // ❌ Same object reference, React might not update

setItem({ ...item, quantity: item.quantity + 1 }); // ✅ New object
```

**❌ Using state immediately after setting**

```typescript
setQuantity(5);
console.log(quantity); // ❌ Still shows old value (state updates are asynchronous)
```

## Next Steps

We can now build interactive components with state, but we're still hardcoding data. In the real POS system, I had arrays of products from the inventory database.

Next, we'll learn how to render **lists of data** dynamically, building a product catalog that displays hundreds of items from an array.

**Continue to**: [Lists & Dynamic Rendering](https://blog.htunnthuthu.com/getting-started/programming/react-101/react-101-lists-keys)
