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:

// 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)

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

Understanding useState

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

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:

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:

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:

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:

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

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:

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:

Event Object and Parameters

Sometimes you need information from the event:

Or with inline arrow function:

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:

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

  • 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

❌ Calling function instead of passing reference

❌ Mutating object state

❌ Using state immediately after setting

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

Last updated