DEV Community

Cover image for The Secret Behind Every Web Feature, No Matter How Complex, Reduces to Three Core Mechanics
Rasheed
Rasheed

Posted on

The Secret Behind Every Web Feature, No Matter How Complex, Reduces to Three Core Mechanics

When I started out as a frontend developer, every new feature felt like a completely different mountain to climb. A like button felt nothing like a shopping cart. A comment section felt completely unrelated to a dark mode toggle. I kept thinking I had to learn a hundred distinct tricks just to survive.

Then I realized something that changed everything.

Every single feature on the web does the exact same three things:

  1. Save the Data
  2. Generate the HTML
  3. Make it Interactive

That's it. That's the whole trick. Instagram's feed, Amazon's cart, your bank's dashboard, this very blog post's comment section — strip away the frameworks, the animations, the design polish, and you're left with the same three-step loop, repeated over and over.

In this post I'll break down what each step actually means, and then we'll prove it by building a small, clean e-commerce UI with React + Zustand, a product list and a shopping cart, step by step, mechanic by mechanic.

Take a look at the output:

the-secret-behind-every-web-feature-no-matter-how-complex-reduces-to-three-core-mechanics

Prerequisites

You don't need to be advanced to follow along, but you'll get the most out of this if you already have:

  • Basic React knowledge — components, props, and useState.
  • Comfort with JavaScript ES6+ — arrow functions, array methods like .map() and .filter(), and the spread operator (...) show up throughout.
  • Node.js installed (v18+) so you can run the Vite dev server.
  • A code editor — VS Code is fine — and a few minutes to follow the terminal commands.

No prior experience with Zustand is required. I'll cover exactly what you need to know as we go.

The Three Mechanics

1. Save the Data

Before anything appears on screen, it has to exist as raw information. A tweet is just an object with text and an author name. A cart is just an array of items with quantities.

- A tweet is { text, author, likes, timestamp }
- A product is { name, price, image, inStock }
- A cart is [{ productId, quantity }]
Enter fullscreen mode Exit fullscreen mode

In a React app, "saving the data" usually means putting it into state — useState, useReducer, or a state management library like Zustand. The data doesn't care how it will be displayed. It's just the source of truth.

2. Generate the HTML

Once you have data, the next job is to turn it into something the browser can render. In React, this is just a function: data goes in, JSX (which becomes HTML) comes out.

products.map(product => <ProductCard product={product} />)

Ten products in your data → ten cards on the screen. Zero products → an empty state. This step is purely mechanical: same data in, same HTML out, every time. That predictability is the whole point of a component-based framework like React.

3. Make it Interactive

Static pages are boring. Interactivity simply means the user does something that changes the underlying data.

  • Click "Add to Cart" → the cart data changes → the cart HTML re-renders
  • Type in a search box → the filter data changes → the list HTML re-renders
  • Toggle dark mode → a boolean changes → the whole UI re-renders

This is the loop: an event handler updates the data, and step 2 runs again automatically. React (and Zustand) exist almost entirely to make this loop fast, predictable, and easy to reason about.

That's the whole secret. A "complex" feature is just this loop, nested and repeated many times with more data shapes involved. Once you see it, you can't unsee it and it becomes a lot easier to plan any feature, because you just ask yourself: what's the data, how do I render it, and what changes it?

Let's build something with this exact mental model.

What We're Building

A small, clean e-commerce screen:

  • A product list (Save the Data → Generate the HTML)
  • An "Add to Cart" button on each product (Make it Interactive)
  • A cart panel showing items, quantities, and a total, with the ability to remove items

We'll use Zustand for state because it maps almost one-to-one onto "Save the Data" — no boilerplate, no reducers, no context providers. Just a store.

Project setup

npm create vite@latest mini-shop -- --template react
_choose Eslint_
cd mini-shop
npm install
npm install zustand
npm run dev
Enter fullscreen mode Exit fullscreen mode

Your folder structure will look like this by the end:

src/
  data/
    products.js
  store/
    useCartStore.js
  components/
    ProductCard.jsx
    ProductList.jsx
    Cart.jsx
  App.jsx
  App.css
Enter fullscreen mode Exit fullscreen mode

Step 1: Save the Data

First, the raw product data. This is just an array, no framework magic yet.

Create a folder inside your src directory named data, and create a file inside it(data) called products.js :

Note: Follow this exact pattern for creating folders and files throughout the entire project.

Create a folder inside your src directory named [folder-name], and create a file inside it called [file-name]:

// src/data/products.js
export const products = [
  { id: 1, name: "Wireless Headphones", price: 59.99, image: "🎧" },
  { id: 2, name: "Mechanical Keyboard", price: 89.99, image: "⌨️" },
  { id: 3, name: "Smart Watch", price: 129.99, image: "" },
  { id: 4, name: "Desk Lamp", price: 24.99, image: "💡" },
  { id: 5, name: "Bluetooth Speaker", price: 45.0, image: "🔊" },
  { id: 6, name: "Backpack", price: 39.99, image: "🎒" },
];
Enter fullscreen mode Exit fullscreen mode

Now let's proceed to the part that matters most: the cart data, and the only place allowed to change it, which is our Zustand store.

A Quick Note on Zustand

Zustand is a small state management library for React. Where useState only lives inside one component, Zustand's store lives outside the component tree entirely , any component can read from it or update it, without prop-drilling or wrapping your app in a Context provider.

The create function builds a hook — in our case, useCartStore. That hook is the store:

  • Call it with a selector, like useCartStore(state => state.items), and a component subscribes to just that slice of data. It only re-renders when items actually changes.
  • Call any function you defined inside the store, like addToCart(product), and it updates the store's state directly — no dispatching actions, no reducers.

That's really it. Compared to useState, it trades "state that belongs to one component" for "state that belongs to the app," which is exactly what a cart needs — the ProductCard that adds an item and the Cart panel that displays it are two completely different components, and Zustand is the shared source of truth connecting them.

// src/store/useCartStore.js

import { create } from "zustand";

const useCartStore = create((set, get) => ({
  items: [], // [{ id, name, price, image, quantity }]

  addToCart: (product) => {
    const existing = get().items.find((item) => item.id === product.id);

    if (existing) {
      set({
        items: get().items.map((item) =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        ),
      });
    } else {
      set({ items: [...get().items, { ...product, quantity: 1 }] });
    }
  },

  removeFromCart: (id) => {
    set({ items: get().items.filter((item) => item.id !== id) });
  },

  totalPrice: () =>
    get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),

  totalItems: () =>
    get().items.reduce((sum, item) => sum + item.quantity, 0),
}));

export default useCartStore;
Enter fullscreen mode Exit fullscreen mode

Notice: this file has zero HTML. It doesn't know or care what a "cart" looks like on screen. It only knows how to hold and change data. That separation is the whole reason step 1 exists on its own — your data logic should survive a complete redesign of your UI untouched.

Step 2: Generate the HTML

Now we turn that data into components. Each component is a pure translation: data in, markup out.

// src/components/ProductCard.jsx
import useCartStore from "../store/useCartStore";

function ProductCard({ product }) {
  const addToCart = useCartStore((state) => state.addToCart);

  return (
    <div className="product-card">
      <div className="product-emoji">{product.image}</div>
      <h3>{product.name}</h3>
      <p className="price">${product.price.toFixed(2)}</p>
      <button onClick={() => addToCart(product)}>Add to Cart</button>
    </div>
  );
}

export default ProductCard;
Enter fullscreen mode Exit fullscreen mode
// src/components/ProductList.jsx
import { products } from "../data/products";
import ProductCard from "./ProductCard";

function ProductList() {
  return (
    <div className="product-grid">
      {products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

export default ProductList;
Enter fullscreen mode Exit fullscreen mode

At this point, if you render in your App.jsx, you'll see six product cards on screen — pure step 2, no interactivity wired up beyond the click handler we're about to explain.

Now the cart's HTML — again, just a translation of items into markup:

// src/components/Cart.jsx
import useCartStore from "../store/useCartStore";

function Cart() {
  const items = useCartStore((state) => state.items);
  const removeFromCart = useCartStore((state) => state.removeFromCart);
  const totalPrice = useCartStore((state) => state.totalPrice());

  if (items.length === 0) {
    return (
      <aside className="cart">
        <h2>Your Cart</h2>
        <p className="empty-cart">Your cart is empty.</p>
      </aside>
    );
  }

  return (
    <aside className="cart">
      <h2>Your Cart</h2>
      <ul className="cart-list">
        {items.map((item) => (
          <li key={item.id} className="cart-item">
            <span className="cart-emoji">{item.image}</span>
            <div className="cart-item-info">
              <p>{item.name}</p>
              <span>
                {item.quantity} × ${item.price.toFixed(2)}
              </span>
            </div>
            <button
              className="remove-btn"
              onClick={() => removeFromCart(item.id)}
            >
              
            </button>
          </li>
        ))}
      </ul>
      <div className="cart-total">
        <strong>Total:</strong> <span>${totalPrice.toFixed(2)}</span>
      </div>
    </aside>
  );
}

export default Cart;
Enter fullscreen mode Exit fullscreen mode

Step 3: Make It Interactive

Here's the thing worth pausing on: we already wrote the interactivity. It happened the moment we called addToCart ** and **removeFromCart inside onClick.

<button onClick={() => addToCart(product)}>Add to Cart</button>

Trace the loop:

  1. - User clicks → addToCart(product) runs
  2. - The Zustand store's items array changes (Save the Data, step 1, happening again)
  3. - Every component subscribed to items — in this case, Cart — automatically re-renders (Generate the HTML, step 2, happening again)

There's no manual DOM manipulation, no "find the cart element and update its innerHTML." React does that reconciliation for you. All we had to write was the part that changes the data. That's the entire secret of interactivity: you never update the screen directly — you update the data, and let step 2 run again.

Let's wire it all together:

// src/App.jsx
import ProductList from "./components/ProductList";
import Cart from "./components/Cart";
import "./App.css";

function App() {
  return (
    <div className="app">
      <header className="app-header">
        <h1>Mini Shop</h1>
      </header>
      <main className="app-main">
        <ProductList />
        <Cart />
      </main>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Making It Look Nice

Let's add a small stylesheet to give this the feel of a real product, without any UI library:

src/App.css

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: #f6f7fb;
  color: #1a1a1a;
}

.app-header {
  background: #111827;
  color: white;
  padding: 1.25rem 2rem;
}

.app-header h1 {
  margin: 0;
  font-size: 1.5rem;
}

.app-main {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: 1.5rem;
  padding: 2rem;
  max-width: 1100px;
  margin: 0 auto;
}

.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
}

.product-card {
  background: white;
  border-radius: 12px;
  padding: 1.25rem;
  text-align: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  transition: transform 0.15s ease;
}

.product-card:hover {
  transform: translateY(-4px);
}

.product-emoji {
  font-size: 2.5rem;
  margin-bottom: 0.5rem;
}

.price {
  color: #6b7280;
  font-weight: 600;
}

.product-card button {
  margin-top: 0.75rem;
  width: 100%;
  padding: 0.5rem;
  border: none;
  border-radius: 8px;
  background: #111827;
  color: white;
  cursor: pointer;
  font-weight: 600;
}

.product-card button:hover {
  background: #374151;
}

.cart {
  background: white;
  border-radius: 12px;
  padding: 1.25rem;
  height: fit-content;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}

.cart h2{
  color: #374151;
}

.empty-cart {
  color: #9ca3af;
}

.cart-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.cart-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid #f0f0f0;
}

.cart-emoji {
  font-size: 1.5rem;
}

.cart-item-info {
  flex: 1;
}

.cart-item-info p {
  margin: 0;
  font-weight: 600;
  font-size: 0.9rem;
}

.cart-item-info span {
  font-size: 0.8rem;
  color: #6b7280;
}

.remove-btn {
  border: none;
  background: #fee2e2;
  color: #dc2626;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  cursor: pointer;
}

.cart-total {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 2px solid #111827;
  display: flex;
  justify-content: space-between;
}

/* ---------- Responsive ---------- */

@media (max-width: 900px) {
  .app-main {
    grid-template-columns: 1fr;
    padding: 1.5rem;
  }

  .cart {
    order: -1; 
  }
}

@media (max-width: 480px) {
  .app-header {
    padding: 1rem 1.25rem;
  }

  .app-header h1 {
    font-size: 1.25rem;
  }

  .app-main {
    padding: 1rem;
    gap: 1rem;
  }

  .product-grid {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 0.75rem;
  }

  .product-card {
    padding: 1rem;
  }

  .product-emoji {
    font-size: 2rem;
  }

  .cart-item {
    gap: 0.5rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

At this point you have a working, good-looking mini shop where products can be added and removed from a cart — all driven by the three-mechanic loop.

Bringing It Full Circle

Look back at what we just built through the lens of the three mechanics:

Save the Data
- products.js (static) and the Zustand items array (dynamic)

Generate the HTML
- ProductList, ProductCard, and Cart — pure functions of data

Make it Interactive
- addToCart / removeFromCart, triggered by onClick, mutating step 1 and triggering step 2 again

That's it. No matter how much you scale this up — checkout flows, payment integration, order history, real-time stock updates — you're still just nesting and repeating this same loop with different data shapes. A "senior" feature isn't a different kind of magic; it's the same three mechanics applied with more data, more edge cases, and more care.

Next time a feature feels overwhelming, don't ask "how do I build this." Ask instead:

  1. What's the data?
  2. How does that data become HTML?
  3. What events change that data?

You'll usually find the "complex" feature was three simple answers wearing a trench coat.

If this mental model clicked for you, I'd love to hear what feature you're going to look at differently now, drop it in the comments. And if you want to see this mini shop extended with a checkout flow, filtering, and persistence, let me know and I'll write a part two.

Top comments (0)