≡ Menu
add to cart feature in javascript

Add to cart ellipse shape red button icon vector. Shopping cart in white background icon.

Adding items to a shopping cart is a foundational feature of any e-commerce site.

While large frameworks like React or Vue handle this beautifully, understanding the core logic with plain JavaScript is crucial.

In this tutorial, we will build a working, in-memory cart using just HTML, CSS (for basic styling), and JavaScript.

🚀 The Three Core Steps

 

The entire process boils down to these three steps:

  1. HTML Setup: Create the product display and the cart counter.

  2. Data Management (JavaScript): Use an array to store and manage cart items (the data structure).

  3. UI Update (JavaScript): Update the on-screen numbers and details based on the stored data.


Step 1: Setting up the HTML Structure

 

We need a product to click and a visible counter for the cart. Notice how we use data-attributes on the button to store the product information directly. This is a clean way to pass data from the HTML element to our JavaScript function.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JS Cart Tutorial</title>
</head>
<body>

    <h1>Simple E-commerce Example</h1>

    <div id="product-1" style="border: 1px solid #ccc; padding: 15px; margin-bottom: 20px;">
        <h2>The JavaScript Handbook</h2>
        <p>A comprehensive guide to modern JS development.</p>
        <p>Price: <strong>$10.00</strong></p>
        
        <button class="add-to-cart" 
                data-product-id="JSH001" 
                data-product-name="The JavaScript Handbook" 
                data-product-price="10.00">
            Add to Cart
        </button>
    </div>

    <hr>

    <div id="cart-summary">
        <h2>🛍️ Shopping Cart Summary</h2>
        <p>Items in Cart: <strong id="cart-count">0</strong></p>
    </div>

    <script src="cart.js"></script> 
</body>
</html>

Step 2: Initializing the Cart and DOM Elements (cart.js)

In our JavaScript file (cart.js), we first set up the variables we’ll need to work with.

// cart.js

/**
 * 1. The Data Structure
 * This array will hold objects representing items in our cart.
 * Example item structure: { id: "JSH001", name: "...", price: 10.00, quantity: 1 }
 */
let cart = []; 

// 2. DOM Elements
const cartCountElement = document.getElementById('cart-count');
const addToCartButtons = document.querySelectorAll('.add-to-cart');

Step 3: Creating the Core Logic Functions

 

We need two main functions: one to update the cart data and one to reflect those changes on the screen.

Function 1: updateCartUI()

This function calculates the total number of items currently in the cart and updates the display.

// Recalculates the total item count and updates the display
function updateCartUI() {
  // Use the .reduce() method to sum the 'quantity' property of every item in the cart array
  const totalItems = cart.reduce((total, item) => total + item.quantity, 0);
  
  cartCountElement.textContent = totalItems;
  
  // Optional: Log the cart for easy debugging in the console
  console.log('Current Cart State:', cart);
}

Function 2: addItemToCart()

This is the central function. It checks if an item is already in the cart. If it is, we just increment the quantity. If it’s new, we add a new item object to the array.

// Handles adding a new item or increasing the quantity of an existing one
function addItemToCart(productId, name, price) {
  // Check if item already exists by searching its ID
  const existingItemIndex = cart.findIndex(item => item.id === productId);

  if (existingItemIndex > -1) {
    // 1. Item exists: Increment its quantity
    cart[existingItemIndex].quantity += 1;
  } else {
    // 2. Item is new: Add a new object to the cart array
    cart.push({
      id: productId,
      name: name,
      price: parseFloat(price), // Important: Convert string price to a number
      quantity: 1
    });
  }

  // After every change to the data, update the display!
  updateCartUI();
}

Step 4: Attaching the Event Listener

The final step is to listen for clicks on our buttons and call the addItemToCart function, pulling the product data from the button’s data-attributes.

// Loop through all buttons and attach a click event listener
addToCartButtons.forEach(button => {
  button.addEventListener('click', () => {
    // Extract data from the clicked button
    const productId = button.dataset.productId;
    const productName = button.dataset.productName;
    const productPrice = button.dataset.productPrice;

    // Call the core logic
    addItemToCart(productId, productName, productPrice);
  });
});

// Run this once when the script loads to initialize the count to '0'
updateCartUI();

🎉 Conclusion and Next Steps

You now have a functional “Add to Cart” button! Click it, and you’ll see the count update instantly.

🌟 Challenge: Making the Cart Persistent

Currently, if you refresh the page, the cart array resets to empty.

In a real-world application, you need the cart to persist.

Your next step should be integrating localStorage to save the cart array data between page loads.

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }

Building a robust comment section is essential for any dynamic content platform, but managing threaded (nested) replies can be tricky.

In this tutorial, we’ll walk through how to build a fully functional, nested comment system using React functional components and hooks.

We will focus on the front-end logic, using a flat array of data and the parentId field to create the recursive structure.

1. Defining the Comment Data Model

The most critical part of a nested comment system is the data structure. We use a flat array where each comment object contains a parentId.

  • parentId: null: This is a top-level comment.
  • parentId: [ID]: This is a reply to the comment with that ID.

data.js

Let’s start with our mock data file, which simulates the initial comments fetched from a database.

// data.js
export const initialComments = [
  // Top-level comments (parentId: null)
  { id: 1, userId: 'userA', content: 'This is the first top-level comment.', parentId: null, timestamp: Date.now() - 50000 },
  { id: 2, userId: 'userB', content: 'I agree, great article!', parentId: null, timestamp: Date.now() - 40000 },

  // Replies
  { id: 3, userId: 'userC', content: 'This is a reply to the first comment.', parentId: 1, timestamp: Date.now() - 30000 },
  { id: 4, userId: 'userA', content: 'My second reply to the first comment.', parentId: 1, timestamp: Date.now() - 20000 },

  // Nested reply (A reply to a reply)
  { id: 5, userId: 'userD', content: 'A reply to comment #3 (a nested reply).', parentId: 3, timestamp: Date.now() - 10000 },
];

2. The Reusable Comment Form

The form component is simple and “dumb”—it only handles collecting the input text and passing it up to the parent component via the onSubmit prop.

CommentForm.jsx

import React, { useState } from 'react';

const CommentForm = ({ parentId = null, onSubmit }) => {
  const [content, setContent] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (content.trim() === '') return;

    // Pass the content back to the parent component
    onSubmit(content);
    setContent('');
  };

  return (
    <form onSubmit={handleSubmit} style={{ margin: '15px 0', padding: '10px', border: '1px solid #ddd', borderRadius: '4px' }}>
      <h4>{parentId ? `Reply to Comment ${parentId}` : 'Post a New Comment'}</h4>
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="Write your comment..."
        rows="3"
        style={{ width: '100%', resize: 'vertical', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' }}
      />
      <button 
        type="submit"
        style={{ padding: '8px 15px', backgroundColor: '#007bff', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}
      >
        Submit Comment
      </button>
    </form>
  );
};

export default CommentForm;

3. The Recursive Comment Item

This is the most powerful part of the system. The CommentItem component calls itself (CommentItem) to render its replies, creating the nesting effect.

CommentItem.jsx

import React, { useState } from 'react';
import CommentForm from './CommentForm';

const CommentItem = ({ comment, allComments, onAddReply }) => {
  const [showReplyForm, setShowReplyForm] = useState(false);

  // 1. Find all direct replies to the current comment
  const replies = allComments.filter(c => c.parentId === comment.id);

  const handleReplySubmit = (content) => {
    // Call the centralized function in the main container, passing the parent ID
    onAddReply(comment.id, content);
    setShowReplyForm(false);
  };

  return (
    <div 
      style={{ 
        marginLeft: comment.parentId ? '20px' : '0', 
        borderLeft: comment.parentId ? '2px solid #ccc' : 'none', 
        paddingLeft: comment.parentId ? '10px' : '0', 
        marginTop: '15px',
        paddingBottom: '10px'
      }}
    >
      <div style={{ backgroundColor: comment.parentId ? '#f9f9f9' : '#fff', padding: '10px', borderRadius: '4px' }}>
        <p style={{ margin: '0 0 5px 0' }}>
          <strong>{comment.userId}</strong> 
          <span style={{ color: '#666', fontSize: '0.8em', marginLeft: '10px' }}>
            ({new Date(comment.timestamp).toLocaleTimeString()})
          </span>
        </p>
        <p style={{ margin: '0 0 10px 0' }}>{comment.content}</p>

        {/* Reply button and form toggler */}
        <button 
          onClick={() => setShowReplyForm(!showReplyForm)}
          style={{ background: 'none', border: 'none', color: '#007bff', cursor: 'pointer', padding: 0 }}
        >
          {showReplyForm ? 'Cancel Reply' : 'Reply'}
        </button>
      </div>

      {/* Conditionally render the reply form */}
      {showReplyForm && (
        <CommentForm parentId={comment.id} onSubmit={handleReplySubmit} />
      )}

      {/* 2. Recursively render replies */}
      {replies.length > 0 && (
        <div style={{ marginTop: '10px' }}>
          {replies.map(reply => (
            <CommentItem
              key={reply.id}
              comment={reply}
              allComments={allComments}
              onAddReply={onAddReply}
            />
          ))}
        </div>
      )}
    </div>
  );
};

export default CommentItem;

4. The Main Comment Container

The CommentsContainer manages the state, initializes the data, and provides the central function (addComment) for creating both new comments and replies.

CommentsContainer.jsx

import React, { useState } from 'react';
import CommentItem from './CommentItem';
import CommentForm from './CommentForm';
import { initialComments } from './data'; 

// Simple counter for generating temporary front-end IDs
let nextId = initialComments.length + 1;

const CommentsContainer = () => {
  // Use state to hold all comments. In a real app, you'd fetch this with useEffect.
  const [comments, setComments] = useState(initialComments);

  const addComment = (parentId, content) => {
    // --- 1. Create the new comment object ---
    const newComment = {
      id: nextId++,
      userId: 'currentUser', // Placeholder for the authenticated user
      content,
      parentId, // This is the ID of the parent comment, or null for top-level
      timestamp: Date.now(),
    };

    // --- 2. Update the state (simulates a successful API response) ---
    setComments(prevComments => [...prevComments, newComment]);

    // **NOTE ON PRODUCTION:**
    // In a real application, this is where you would call your API (e.g., fetch('/api/comments', { method: 'POST', body: JSON.stringify(newComment) })).
    // You would typically update the state *after* the server confirms the comment was saved.
  };

  // Filter and sort to show only top-level comments for the initial list
  const topLevelComments = comments
    .filter(c => c.parentId === null)
    .sort((a, b) => b.timestamp - a.timestamp); // Sort by newest first

  return (
    <div style={{ maxWidth: '800px', margin: '0 auto', padding: '30px', fontFamily: 'Inter, sans-serif', backgroundColor: '#f4f4f9', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.05)' }}>
      <h1 style={{ borderBottom: '2px solid #ddd', paddingBottom: '10px', marginBottom: '20px', color: '#333' }}>
        Article Comments ({comments.length})
      </h1>
      
      {/* Main Comment Form (parentId: null) */}
      <CommentForm 
        onSubmit={(content) => addComment(null, content)} 
        parentId={null} 
      />

      {/* Render the top-level comments, which recursively render their children */}
      <div style={{ marginTop: '30px' }}>
        {topLevelComments.map(comment => (
          <CommentItem
            key={comment.id}
            comment={comment}
            allComments={comments} // Pass the entire list for lookups
            onAddReply={addComment} // Pass the centralized function
          />
        ))}

        {topLevelComments.length === 0 && <p style={{ textAlign: 'center', color: '#666' }}>Be the first to share your thoughts!</p>}
      </div>
    </div>
  );
};

export default CommentsContainer;

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }