DEV Community

Agunrege Oyinkan
Agunrege Oyinkan

Posted on

Create a Powerful E-commerce Search and Filtering System with React

When building an ecommerce website, developers are often presented with extensive list of items/products. But as a developer, you need to ensure that users are able to items swiftly without having to navigate through extensive list. As developers, integrating search input for real-time filtering and enabling users locate product by categories or prices will make users get a better shopping experience on our E-commerce website. While building a Full Stack Ecommerce Website using MERN Stack and i decided to make a Powerful Search and Filter Website

Here's a visual representation of what you are going to build

Image description
In this tutorial, we will learn to build a powerful filtering system in React. You'll learn how to:

Filter by Category: Allow users to narrow down items based on specific categories.

Filter by Size: Enable filtering of products by size attributes.

Sort by Price: Implement sorting functionality to arrange items from high to low prices and vice versa.

Search Filter: Integrate a search bar for real-time filtering based on user input.

Here's a visual representation of what you are going to build

Let's dive into the actual development.

Ensure you have basic react knowledge to implement this properly.

This is the code structure.

Navbar.jsx

import React, {useContext, useState} from 'react'
import {BiUser, BiCart} from 'react-icons/bi'
import './Navbar.css'
import { ShopContext } from '../context/ShopContext'


const Navbar = () => {

    const { updateSearchTerm } = useContext(ShopContext)

    const [searchInput, setSearchInput] = useState('')


    const handleSearch = () => {
        updateSearchTerm(searchInput);
    }

  return (
    <div>
        <nav className="navbar">
            <div className="nav-top">
                <h2>Luma</h2>
                <div className="search-bar">
                    <input type="text" value={searchInput} onChange={(e) => setSearchInput(e.target.value)} className='search-input' placeholder='Search for products....' />
                    <button onClick={handleSearch} className="search-btn">Search</button>
                </div>
                <div className="icons">
                    <div className="profile-group">
                        <BiUser className='icon'/>
                    </div>
                    <div className="cart-icon">
                        <BiCart className='icon' />
                        <span className="cart-count">0</span>
                    </div>
                </div>
            </div>
        </nav>
    </div>
  )
}

export default Navbar

Enter fullscreen mode Exit fullscreen mode

ProductFilter.jsx

import React, {useState, useEffect, useContext} from 'react'
import { ShopContext } from '../context/ShopContext';

import './ProductFilter.css'

const ProductFilter = () => {

    const {products, searchTerm } = useContext(ShopContext);
    const [displayFilter, setDisplayFilter] = useState(false);
    const [filteredProduct, setFilteredProduct] =useState([]);
    const [category, setCategory] = useState([]);
    const [sizeCategory, setSizeCategory] = useState([]);
    const [material, setMaterialCategory] = useState([]);
    const [sortType, setSortType] = useState('relevant');
Enter fullscreen mode Exit fullscreen mode

  // Function to toggle the selected categories in the filter
  const toggleCategory = (e) => {
    // Check if the clicked category is already selected
    if (category.includes(e.target.value)) {
      // If the category is selected, remove it from the selected categories array
      setCategory(prev => prev.filter(item => item !== e.target.value));
    } else {
      // If the category is not selected, add it to the selected categories array
      setCategory(prev => [...prev, e.target.value]);
    }
  };

  // Function to toggle the selected sizecategories in the filter
  const togglesizeCategory = (e) => {
    // Check if the clicked sizecategory is already selected
    if (sizeCategory.includes(e.target.value)) {
      // If the sizecategory is selected, remove it from the selected sizecategories array
      setSizeCategory(prev => prev.filter(item => item !== e.target.value));
    } else {
      // If the sizecategory is not selected, add it to the selected sizecategories array
      setSizeCategory(prev => [...prev, e.target.value]);
    }
  };
Enter fullscreen mode Exit fullscreen mode
// Function to toggle the selected sizecategories in the filter
   const toggleMaterialCategory = (e) => {
    // Check if the clicked sizecategory is already selected
    if (material.includes(e.target.value)) {
      // If the sizecategory is selected, remove it from the selected material array
      setMaterialCategory(prev => prev.filter(item => item !== e.target.value));
    } else {
      // If the Materialcategory is not selected, add it to the selected Materialcategories array
      setMaterialCategory(prev => [...prev, e.target.value]);
    }
  };
Enter fullscreen mode Exit fullscreen mode

  // Function to apply filters on the product list based on category, sizecategory, and search criteria
  const filterResults = () => {
    // Create a copy of the original products array to avoid mutating the original data
    let duplicateProduct = products.slice();

    // If search is enabled (showSearch is true) and there's a search term
    if (searchTerm) {
      // Filter products by checking if the product name includes the search term (case-insensitive)
      duplicateProduct = duplicateProduct.filter(item =>
        item.name.toLowerCase().includes(searchTerm.toLowerCase())
      );
    }

    // If there are selected categories, filter products to include only those categories
    if (category.length > 0) {
      duplicateProduct = duplicateProduct.filter(item => category.includes(item.category));
    }

    // If there are selected sizecategories, filter products to include only those sizecategories
    if (sizeCategory.length > 0) {
      duplicateProduct = duplicateProduct.filter(item => sizeCategory.includes(item.sizeCategory));
    }

    // If there are selected sizecategories, filter products to include only those sizecategories
    if (material.length > 0) {
      duplicateProduct = duplicateProduct.filter(item => material.includes(item.material));
    }

    // Update the filtered product list state with the filtered products
    setFilteredProduct(duplicateProduct);
  };

Enter fullscreen mode Exit fullscreen mode
  // Function to sort the filtered product list based on the selected sorting type
  const sortProduct = () => {
    // Create a copy of the filtered products to avoid mutating the state directly
    let fpCopy = filteredProduct.slice();

    // Sort products based on the sorting type
    switch (sortType) {
      case 'low-high':
        // Sort by price in ascending order
        setFilteredProduct(fpCopy.sort((a, b) => a.price - b.price));
        break;

      case 'high-low':
        // Sort by price in descending order
        setFilteredProduct(fpCopy.sort((a, b) => b.price - a.price));
        break;

      default:
        // If no sort type is selected, reapply the filters
        filterResults();
        break;
    }
  };

Enter fullscreen mode Exit fullscreen mode

  // useEffect to reapply filters whenever any of the dependencies (category, sizeCategory, search, etc.) change
  useEffect(() => {
    filterResults();
  }, [category, sizeCategory,material, searchTerm, products]);

  // useEffect to sort the filtered products whenever the sorting type changes
  useEffect(() => {
    sortProduct();
  }, [sortType]);

Enter fullscreen mode Exit fullscreen mode
return (
    <>
    <div className="filter-and-products-container">
      {/* Filter Option */}
      <div className="filter-container">
        <p 
          onClick={() => setDisplayFilter(!displayFilter)} 
          className="filter-title"
        >
          FILTERS
        </p>
        {/* Category Filter */}
        <div className={`filter-section ${displayFilter ? '' : 'hidden'}`}>
        <p className="filter-sizetitle">GENDER</p>
          <p className="filter-item">
            <input type="checkbox" onChange={toggleCategory} value="Men" /> Men
          </p>
          <p className="filter-item">
            <input type="checkbox" onChange={toggleCategory} value="Women" /> Women
          </p>
          <p className="filter-item">
            <input type="checkbox" onChange={toggleCategory} value="Kids" /> Kids
          </p>
        </div>
        {/* sizecategory Filter */}
        <div className={`filter-section ${displayFilter ? '' : 'hidden'}`}>
          <p className="filter-sizetitle">CLOTHING SIZE</p>
          <div className="filter-sizecategory">
            <p className="filter-item">
              <input type="checkbox" onChange={togglesizeCategory} value="Juniors" /> Juniors
            </p>
            <p className="filter-item">
              <input type="checkbox" onChange={togglesizeCategory} value="Petite" /> Petite
            </p>
            <p className="filter-item">
              <input type="checkbox" onChange={togglesizeCategory} value="Plussize" /> Plus Size
            </p>
          </div>
        </div>
Enter fullscreen mode Exit fullscreen mode
 {/* Material filter */}
        <div className={`filter-section ${displayFilter ? '' : 'hidden'}`}>
          <p className="filter-sizetitle">MATERIAL</p>
          <div className="filter-sizecategory">
            <p className="filter-item">
              <input type="checkbox" onChange={toggleMaterialCategory} value="Cotton" /> Cotton
            </p>
            <p className="filter-item">
              <input type="checkbox" onChange={toggleMaterialCategory} value="Leather" /> Leather
            </p>
            <p className="filter-item">
              <input type="checkbox" onChange={toggleMaterialCategory} value="Silk" /> Silk
            </p>
            <p className="filter-item">
              <input type="checkbox" onChange={toggleMaterialCategory} value="Suede" /> Suede
            </p>
          </div>
        </div>
      </div>
Enter fullscreen mode Exit fullscreen mode
  {/* Right Side */}
      <div className="products-container">
        <div className="products-header">
          <h2>All Collection</h2>
          <select 
            onChange={(e) => setSortType(e.target.value)} 
            className="sort-dropdown"
          >
            <option value="relevant">Sort by: Relevant</option>
            <option value="low-high">Sort by: Low to High</option>
            <option value="high-low">Sort by: High to Low</option>
          </select>
        </div>
        {/* Map Products */}
        <div className="product-grid">
          {filteredProduct.map((product) => (
            <div className="product-card" key={product._id}>
              <div className="product-image">
                <img src={product.image[0]} alt={product.name} />
              </div>
              <h3>{product.name}</h3>
              <p>${product.price}</p>
            </div>
          ))}
        </div>
          </div>
          </div>
    </>
  )
}

export default ProductFilter
Enter fullscreen mode Exit fullscreen mode

ShoContext.jsx

// Import necessary functions and data from React and local assets
import { createContext, useState } from "react";
import { product } from '../assets/assets'

// Create a new context named ShopContext
export const ShopContext = createContext()

// Define the ShopContextProvider component that will wrap child components
const ShopContextProvider = ({ children }) => {

    // Initialize the products state with data imported from assets
    const [products, setProducts] = useState(product)

    // Initialize the searchTerm state to manage the search input
    const [searchTerm, setSearchTerm] = useState('')

    // Function to update the searchTerm state
    const updateSearchTerm = (term) => {
        setSearchTerm(term)
    }

    // Define the value object containing state and updater function
    const value = {
        products,        // Current list of products
        searchTerm,     // Current search term
        updateSearchTerm // Function to update the search term
    }

    // Return the context provider wrapping the child components
    return (
        <ShopContext.Provider value={value}>
            {children}
        </ShopContext.Provider>
    )
}

// Export the ShopContextProvider component as the default export
export default ShopContextProvider;

Enter fullscreen mode Exit fullscreen mode

For a video tutorial, check this video

Top comments (0)