Product Listing, Filtering & Search
Relevant source files
The following files were used as context for generating this wiki page:- app/(shop)/products/page.tsx
- features/products/components/products-content.tsx
- features/products/hooks/use-products.ts
- features/products/services/products.service.ts
- features/products/components/product-filters.tsx
- types/product.types.ts
- features/products/utils/index.ts
- features/products/components/product-filters-sidebar.tsx
The Product Listing feature is the primary interface for users to browse, search, filter, and sort products in the shop. It is a client-rendered page that dynamically fetches product data based on user interactions and synchronizes its state with the URL for shareability and navigation.
The system is designed with a clear separation of concerns, using a feature-based architecture. It combines server-side filtering for primary data reduction with client-side manipulation for enhanced user experience, such as real-time sorting. State management is handled within the main content component, which orchestrates data fetching and UI updates across various child components.
Architecture Overview
The feature follows a layered architecture, where the UI components trigger data fetching via a custom hook, which in turn uses a dedicated service to communicate with the backend API.
The diagram below illustrates the high-level data flow from the page component down to the API service.
Core Components
The user interface is composed of several specialized React components, orchestrated by ProductsContent.
| Component | File Path | Description |
|---|---|---|
ProductsContent | features/products/components/products-content.tsx | The main stateful component. Manages all filter, search, sort, and pagination state. Fetches data using the useProducts hook and passes data and handlers to children. |
ProductFiltersSidebar | features/products/components/product-filters-sidebar.tsx | A desktop-only sidebar that wraps ProductFilters in a sticky container. |
ProductFilters | features/products/components/product-filters.tsx | Renders the filter controls for price range and categories. Includes a search bar for categories. |
ProductSearchBar | features/products/components/product-search-bar.tsx | A simple input component for handling the text-based product search. |
ProductGrid / ProductList | features/products/components/product-grid.tsx / product-list.tsx | Display the fetched products in either a grid or list view, based on the viewMode state. |
ProductsPagination | features/products/components/products-pagination.tsx | Renders pagination controls, allowing the user to navigate through pages of products. |
ProductSort | features/products/components/product-sort.tsx | A dropdown menu for selecting the client-side sorting criteria (e.g., price, newest). |
Sources: features/products/components/products-content.tsx:10-20, features/products/components/product-filters-sidebar.tsx, features/products/components/product-filters.tsx
Data Fetching and State Management
Data fetching is managed through a custom hook and a service layer, with component state synchronized to the URL.
Data Flow Sequence
The following sequence diagram shows the interaction between the user, components, hooks, and services when a filter is applied.
useProducts Hook
The useProducts hook is the primary mechanism for fetching product data. It's a simple useEffect-based hook that abstracts the API call logic.
- Purpose: To fetch a paginated and filtered list of products from the API.
- Parameters: It accepts a
paramsobject that maps directly to the API query parameters. - Returns: An object containing
products,totalcount,loadingstate, anderrormessage.
// features/products/hooks/use-products.ts:13-20
interface UseProductsParams {
page?: number;
pageSize?: number;
categorySlug?: string;
search?: string;
minPriceInt?: number;
maxPriceInt?: number;
}
Sources: features/products/hooks/use-products.ts:1-49
ProductsService
The ProductsService class encapsulates all communication with the /products API endpoint. The getAll method is responsible for building and executing the request for the product list.
- It constructs a
URLSearchParamsobject from theGetProductsParams. - It makes a GET request to the
/productsendpoint. - It uses
apiGetValidatedto ensure the API response conforms to thePaginatedProductsSchema.
Sources: features/products/services/products.service.ts:54-84
API Query Parameters
The /products endpoint accepts several query parameters for filtering and pagination.
| Parameter | Type | Description | Example |
|---|---|---|---|
page | number | The page number to retrieve. | page=2 |
pageSize | number | The number of products per page. | pageSize=12 |
categorySlug | string | Filters products by a single category slug. | categorySlug=gong |
search | string | Performs a text search on product fields. | search=aviator |
minPriceInt | number | The minimum product price (integer value). | minPriceInt=50000 |
maxPriceInt | number | The maximum product price (integer value). | maxPriceInt=200000 |
Sources: features/products/services/products.service.ts:57-82, features/products/hooks/use-products.ts:13-20
URL State Synchronization
The ProductsContent component synchronizes its state with the browser's URL query string.
- On initial load,
useEffectreadssearch,categorySlug,page, andpageSizefrom the URL to initialize the component's state. - Whenever state variables (
search,selectedCategories,page,pageSize) change, anotheruseEffecthook updates the URL. - This update is debounced by 300ms to prevent excessive history pushes while the user is interacting with filters.
- The
router.replacemethod is used withscroll: falseto update the URL without reloading the page or changing the scroll position.
Sources: features/products/components/products-content.tsx:50-84, 201-224
Filtering and Sorting Logic
The feature employs a hybrid approach, using both server-side and client-side logic.
Server-Side Filtering
The following filters are applied on the server by passing them as query parameters to the API:
- Search term (
search) - Single category (
categorySlug) - Price range (
minPriceInt,maxPriceInt) - Pagination (
page,pageSize)
This is the primary method of data reduction, ensuring that only relevant data is sent to the client.
Sources: features/products/components/products-content.tsx:101-107, features/products/services/products.service.ts:54-84
Client-Side Logic
After the initial dataset is fetched from the server, some operations are performed on the client for a more responsive UX:
- Multi-Category Filtering: If more than one category is selected in the filters, the filtering logic is executed on the client-side
productsarray. The API is only queried with a singlecategorySlugif just one is selected. - Sorting: Product sorting (by price, newest) is handled entirely on the client. When a sort option is selected, the
filteredProductsarray is re-sorted within auseMemohook.
This client-side logic is triggered when selectedCategories.length > 1 or sortBy has a value. When this happens, pagination is also calculated on the client-side filteredProducts array.
Sources: features/products/components/products-content.tsx:142-171, 178-188
Data Models
The core data structure is the Product, which is defined using TypeScript interfaces and Zod schemas for runtime validation.
Product Entity Relationship
The Product has an optional relationship with a Category.
Sources: types/product.types.ts:93-132
Product Interface
The Product interface defines the shape of a product object.
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (CUID). |
sku | string | Stock Keeping Unit. |
name | string | Product name. |
slug | string | URL-friendly slug. |
priceInt | number | Price in integer format (e.g., 199900 for 199,900 VND). |
stock | number | Available quantity. |
description | string | null | Product description. |
imageUrls | string[] | Array of image URLs. |
categoryId | string | null | Foreign key for the category. |
attributes | ProductAttributes | null | Object containing detailed product attributes. |
model3dUrl | string | null | URL for a 3D model of the product. |
category | Category | null | Optional populated category object. |
Sources: types/product.types.ts:93-107