Paramclasses Academy - Blog

Tutorials Details

←
Post 26 of 29
→

Project 3 — Building a Product Listing Application with Search and Filtering in react.js

Project 3 — Building a Product Listing Application with Search and Filtering in react.js

October 30, 2026

1. Introduction

In this tutorial, we will build a professional Product Listing Application using React.js.

A Product Listing Application displays products in an organized layout and allows users to search, filter, and sort products according to their preferences.

For example, an e-commerce website may display hundreds of products. Users should be able to find a particular product without manually searching through the entire list.

We will create a product catalog containing product images, names, prices, categories, and ratings.

Users will be able to:

  • View all products.

  • Search products by name.

  • Filter products by category.

  • Sort products by price.

  • Sort products alphabetically.

  • View product details.

  • Add products to a shopping cart.

  • See the number of products displayed.

This project will help you understand how React is used to build interactive e-commerce interfaces.

Technologies Used:

  • React.js

  • JavaScript ES6+

  • HTML5

  • CSS3

  • Vite

  • React Hooks

  • DummyJSON Products API

2. Learning Objectives

After completing this tutorial, you will learn:

  1. How to create a React project using Vite.

  2. How to fetch product data from an API.

  3. How to display products dynamically.

  4. How to create reusable product components.

  5. How to implement search functionality.

  6. How to filter products by category.

  7. How to sort products by price.

  8. How to implement alphabetical sorting.

  9. How to manage shopping cart state.

  10. How to handle loading and error states.

  11. How to use JavaScript map(), filter(), and sort().

  12. How to create a responsive product catalog.

3. Understanding Product Listing Applications

What is a Product Listing Application?

A Product Listing Application is a software interface that displays products in a structured format so users can explore and find items.

Examples include:

  • Amazon

  • Flipkart

  • Shopify stores

  • Online electronics stores

  • Fashion shopping websites

What is Product Filtering?

Product filtering means displaying only products that match selected conditions.

Example:

If a user selects the Electronics category, the application displays only products belonging to that category.

What is Product Sorting?

Product sorting means arranging products in a particular order.

Examples:

  • Price: Low to High

  • Price: High to Low

  • Name: A to Z

  • Name: Z to A

What is Product Searching?

Product searching allows users to find products by entering keywords.

For example, if a user searches for "Laptop", the application displays matching products.

4. Create React Project

Open your terminal and execute:

npm create vite@latest product-app -- --template react

Move into the project directory:

cd product-app

Install dependencies:

npm install

Start the development server:

npm run dev

Open the local URL displayed in your terminal.

5. Understanding the Products API

We will use the DummyJSON Products API to retrieve sample product data.

Official API documentation:

https://dummyjson.com/docs/products

Get All Products

https://dummyjson.com/products

Get Products by Category

https://dummyjson.com/products/category/smartphones

Get Available Categories

https://dummyjson.com/products/categories

The API provides product information such as:

  • Product ID

  • Product Title

  • Product Description

  • Product Price

  • Product Category

  • Product Thumbnail

  • Product Rating

  • Product Images

The API provides sample product data for development and learning purposes.

6. Project Folder Structure

Organize your application like this:

product-app/
│
├── src/
│   ├── components/
│   │   ├── ProductCard.jsx
│   │   ├── ProductList.jsx
│   │   └── FilterBar.jsx
│   │
│   ├── App.jsx
│   ├── App.css
│   ├── index.css
│   └── main.jsx
│
├── package.json
└── index.html

Component responsibilities:

  • ProductCard: Displays an individual product.

  • ProductList: Displays all matching products.

  • FilterBar: Provides search, category, and sorting controls.

  • App: Manages API data, filters, sorting, and cart state.

7. Build the Product Listing Application

Step 1: Create ProductCard Component

Create src/components/ProductCard.jsx.

function ProductCard({ product, onAddToCart }) {
  return (
    <div className="product-card">

      <div className="product-image">
        <img
          src={product.thumbnail}
          alt={product.title}
        />
      </div>

      <div className="product-details">

        <span className="product-category">
          {product.category}
        </span>

        <h3>{product.title}</h3>

        <p className="product-description">
          {product.description}
        </p>

        <div className="product-rating">
          ⭐ {product.rating}
        </div>

        <div className="product-footer">

          <strong>
            ${product.price.toFixed(2)}
          </strong>

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

        </div>

      </div>

    </div>
  );
}

export default ProductCard;

Explanation:

  • product contains individual product information.

  • product.thumbnail displays the product image.

  • product.title displays the product name.

  • product.price displays the product price.

  • product.rating displays the product rating.

  • onAddToCart() sends the selected product to the parent component.

Step 2: Create ProductList Component

Create src/components/ProductList.jsx.

import ProductCard from "./ProductCard";

function ProductList({ products, onAddToCart }) {

  if (products.length === 0) {
    return (
      <div className="no-products">
        <h2>No Products Found</h2>
        <p>Try changing your search or filters.</p>
      </div>
    );
  }

  return (
    <div className="product-grid">

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

    </div>
  );
}

export default ProductList;

Explanation:

  • map() creates a product card for every product.

  • key provides a unique identity to each product.

  • ProductCard keeps the interface reusable.

  • Conditional rendering displays a message when no products match.

Step 3: Create FilterBar Component

Create src/components/FilterBar.jsx.

function FilterBar({
  search,
  setSearch,
  category,
  setCategory,
  sort,
  setSort,
  categories
}) {
  return (
    <div className="filter-bar">

      <input
        type="text"
        placeholder="Search products..."
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      <select
        value={category}
        onChange={(e) => setCategory(e.target.value)}
      >
        <option value="all">All Categories</option>

        {categories.map((item) => (
          <option key={item.slug} value={item.slug}>
            {item.name}
          </option>
        ))}

      </select>

      <select
        value={sort}
        onChange={(e) => setSort(e.target.value)}
      >
        <option value="default">Default Sorting</option>
        <option value="low-high">Price: Low to High</option>
        <option value="high-low">Price: High to Low</option>
        <option value="a-z">Name: A to Z</option>
        <option value="z-a">Name: Z to A</option>
      </select>

    </div>
  );
}

export default FilterBar;

Explanation:

  • search stores the search keyword.

  • category stores the selected category.

  • sort stores the selected sorting option.

  • categories contains available category information.

  • setSearch(), setCategory(), and setSort() update the parent component's state.

8. Create Main App Component

Open src/App.jsx.

import { useEffect, useState } from "react";

import ProductList from "./components/ProductList";
import FilterBar from "./components/FilterBar";

import "./App.css";

function App() {

  const [products, setProducts] = useState([]);
  const [categories, setCategories] = useState([]);

  const [search, setSearch] = useState("");
  const [category, setCategory] = useState("all");
  const [sort, setSort] = useState("default");

  const [cart, setCart] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");

  // Fetch Products and Categories
  useEffect(() => {

    const fetchData = async () => {

      try {

        setLoading(true);
        setError("");

        const [productResponse, categoryResponse] =
          await Promise.all([
            fetch("https://dummyjson.com/products?limit=100"),
            fetch("https://dummyjson.com/products/categories")
          ]);

        if (!productResponse.ok || !categoryResponse.ok) {
          throw new Error("Unable to load product data.");
        }

        const productData = await productResponse.json();
        const categoryData = await categoryResponse.json();

        setProducts(productData.products);
        setCategories(categoryData);

      } catch (err) {

        setError(err.message);

      } finally {

        setLoading(false);

      }
    };

    fetchData();

  }, []);

  // Add Product to Cart
  const addToCart = (product) => {

    setCart((prevCart) => {

      const existingProduct = prevCart.find(
        (item) => item.id === product.id
      );

      if (existingProduct) {
        return prevCart.map((item) =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      }

      return [
        ...prevCart,
        { ...product, quantity: 1 }
      ];

    });

  };

  // Search, Filter and Sort Products
  const filteredProducts = products
    .filter((product) => {

      const matchesSearch =
        product.title
          .toLowerCase()
          .includes(search.toLowerCase());

      const matchesCategory =
        category === "all" ||
        product.category === category;

      return matchesSearch && matchesCategory;

    })
    .sort((a, b) => {

      if (sort === "low-high") {
        return a.price - b.price;
      }

      if (sort === "high-low") {
        return b.price - a.price;
      }

      if (sort === "a-z") {
        return a.title.localeCompare(b.title);
      }

      if (sort === "z-a") {
        return b.title.localeCompare(a.title);
      }

      return 0;

    });

  const cartCount = cart.reduce(
    (total, item) => total + item.quantity,
    0
  );

  return (
    <div className="app-container">

      <header className="main-header">

        <div className="brand">
          <h1>ShopZone</h1>
          <p>Discover amazing products</p>
        </div>

        <div className="cart-display">
          ???? Cart ({cartCount})
        </div>

      </header>

      <main className="main-content">

        <section className="hero-section">
          <h2>Explore Our Products</h2>
          <p>
            Find your favorite products at great prices.
          </p>
        </section>

        <FilterBar
          search={search}
          setSearch={setSearch}
          category={category}
          setCategory={setCategory}
          sort={sort}
          setSort={setSort}
          categories={categories}
        />

        <div className="result-count">
          Showing {filteredProducts.length} products
        </div>

        {loading && (
          <p className="status-message">
            Loading products...
          </p>
        )}

        {error && (
          <p className="error-message">
            {error}
          </p>
        )}

        {!loading && !error && (
          <ProductList
            products={filteredProducts}
            onAddToCart={addToCart}
          />
        )}

      </main>

    </div>
  );
}

export default App;

Understanding the Main Logic

1. Promise.all()

Promise.all() allows multiple Promises to run concurrently.

We use it to fetch products and categories at the same time.

2. Search Functionality

product.title
  .toLowerCase()
  .includes(search.toLowerCase())

This checks whether the product title contains the entered search keyword, without case sensitivity.

3. Category Filtering

category === "all" ||
product.category === category

This displays all products when the selected category is all; otherwise, it displays matching products.

4. Price Sorting

a.price - b.price

Sorts prices from low to high.

b.price - a.price

Sorts prices from high to low.

5. Alphabetical Sorting

a.title.localeCompare(b.title)

Sorts product names alphabetically.

6. Shopping Cart Count

const cartCount = cart.reduce(
  (total, item) => total + item.quantity,
  0
);

This calculates the total number of items in the cart, including quantities.

9. Add CSS Styling

Open src/App.css.

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: Arial, sans-serif;
  background: #f1f5f9;
  color: #1e293b;
}

.app-container {
  min-height: 100vh;
}

.main-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 6%;
  background: #172554;
  color: white;
}

.brand h1 {
  margin: 0;
  font-size: 28px;
}

.brand p {
  margin-top: 5px;
  color: #bfdbfe;
}

.cart-display {
  background: #4f46e5;
  padding: 12px 18px;
  border-radius: 8px;
  font-weight: bold;
}

.main-content {
  max-width: 1250px;
  margin: auto;
  padding: 35px 20px;
}

.hero-section {
  text-align: center;
  padding: 35px 10px;
}

.hero-section h2 {
  font-size: 34px;
  margin-bottom: 10px;
  color: #172554;
}

.hero-section p {
  color: #64748b;
}

.filter-bar {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 15px;
  margin: 25px 0;
}

.filter-bar input,
.filter-bar select {
  width: 100%;
  padding: 13px;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  background: white;
  font-size: 15px;
}

.result-count {
  margin: 20px 0;
  font-weight: bold;
  color: #475569;
}

.product-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 22px;
}

.product-card {
  background: white;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 5px 18px rgba(0, 0, 0, 0.07);
  transition: transform 0.2s;
}

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

.product-image {
  height: 220px;
  background: #f8fafc;
  padding: 15px;
}

.product-image img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.product-details {
  padding: 18px;
}

.product-category {
  display: inline-block;
  color: #4f46e5;
  background: #eef2ff;
  padding: 5px 9px;
  border-radius: 5px;
  font-size: 12px;
  text-transform: capitalize;
}

.product-details h3 {
  margin: 12px 0;
  font-size: 18px;
}

.product-description {
  color: #64748b;
  font-size: 14px;
  line-height: 1.5;
  min-height: 42px;
}

.product-rating {
  margin: 12px 0;
  color: #d97706;
}

.product-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
}

.product-footer strong {
  font-size: 21px;
  color: #172554;
}

.product-footer button {
  padding: 10px 13px;
  background: #4f46e5;
  color: white;
  border: none;
  border-radius: 7px;
  cursor: pointer;
}

.product-footer button:hover {
  background: #3730a3;
}

.no-products {
  text-align: center;
  padding: 60px 15px;
  background: white;
  border-radius: 12px;
}

.status-message,
.error-message {
  text-align: center;
  padding: 20px;
}

.error-message {
  color: #b91c1c;
  background: #fee2e2;
  border-radius: 8px;
}

@media (max-width: 850px) {
  .product-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .filter-bar {
    grid-template-columns: 1fr 1fr;
  }

  .filter-bar input {
    grid-column: 1 / -1;
  }
}

@media (max-width: 550px) {
  .main-header {
    padding: 16px;
  }

  .brand h1 {
    font-size: 22px;
  }

  .main-content {
    padding: 20px 12px;
  }

  .hero-section h2 {
    font-size: 26px;
  }

  .product-grid {
    grid-template-columns: 1fr;
  }

  .filter-bar {
    grid-template-columns: 1fr;
  }

  .filter-bar input {
    grid-column: auto;
  }
}

10. Configure main.jsx

Open src/main.jsx.

import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App.jsx";
import "./index.css";

ReactDOM.createRoot(
  document.getElementById("root")
).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Open src/index.css.

* {
  margin: 0;
  padding: 0;
}

body {
  font-family: Arial, sans-serif;
}

11. How the Application Works

The application follows this workflow:

  1. React application starts.

  2. useEffect requests product and category data from DummyJSON.

  3. API returns product information.

  4. useState stores the received information.

  5. ProductList displays the products.

  6. User enters a search keyword.

  7. filter() selects matching products.

  8. Category selection applies an additional filter.

  9. sort() arranges the matching products.

  10. ProductCard displays the final results.

  11. Add to Cart updates the cart state.

  12. reduce() calculates the cart quantity.

12. Common Mistakes and Best Practices

Common Mistakes

  1. Forgetting to check the API response status.

  2. Not handling loading and error states.

  3. Forgetting unique keys in product lists.

  4. Modifying state arrays directly.

  5. Using the wrong category value.

  6. Sorting the original state array directly.

  7. Forgetting to handle an empty search result.

  8. Not using responsive layouts.

Best Practices

  • Use reusable components.

  • Keep API requests separate from presentation components.

  • Use meaningful variable names.

  • Use immutable state updates.

  • Handle API errors properly.

  • Use unique product IDs as React keys.

  • Keep search and filtering logic organized.

  • Use responsive CSS Grid.

  • Avoid unnecessary state variables when data can be derived from existing state.

Important React Concept: JavaScript sort() modifies the original array. In our application, we first create a new array using filter(), then sort that derived array. This prevents accidental modification of the original products state.

13. Practice Exercises

Try completing these exercises yourself:

  1. Add a minimum and maximum price filter.

  2. Add a rating filter.

  3. Add a discount percentage filter.

  4. Add a product availability filter.

  5. Add a button to clear all filters.

  6. Add product pagination.

  7. Display the total cart price.

  8. Add quantity increase and decrease buttons.

  9. Add a product details page.

  10. Save cart data in Local Storage.

14. Key Takeaways and Conclusion

In this tutorial, we built a Product Listing Application using React.js and DummyJSON.

You learned:

  • How to fetch product data from an API.

  • How to display dynamic products.

  • How to create reusable components.

  • How to implement search functionality.

  • How to filter products by category.

  • How to sort products by price and name.

  • How to manage cart state.

  • How to use map(), filter(), sort(), and reduce().

  • How to handle loading and error states.

  • How to create responsive product layouts.

These concepts are widely used in professional e-commerce applications.

←
Post 26 of 29
→