@W-18820722 Refactor PDP for better readability and maintainability#2668
@W-18820722 Refactor PDP for better readability and maintainability#2668kevinxh merged 20 commits intofeature/chakra-ui-upgrade-v3from
Conversation
🎉 Snyk checks have passed. No issues have been found so far.✅ security/snyk check is complete. No issues have been found. (View Details) ✅ license/snyk check is complete. No issues have been found. (View Details) |
vmarta
left a comment
There was a problem hiding this comment.
I saw a few broken things when testing the PDP as described in the PR description:
- the useDataCloud call seems to run more than once. I expected to see an error once in the console. The error itself I believe is already fixed in the develop branch. But in this case, seeing multiple errors perhaps indicate that the hook is called on every render?
- Both product bundles and sets are broken. Clicking on the main CTA button does not do anything. It returns an error in the console.
vmarta
left a comment
There was a problem hiding this comment.
My main concern is I feel the useProductDetailData returns too much. But anyways, I leave it up to you whether that's worth updating or not. If yes, then perhaps we can also create a new ticket to address it later.
But other than that, the PR looks good to me 👍 Thanks for fixing a few things reported earlier.
| <ProductDetails | ||
| product={product} | ||
| primaryCategory={primaryCategory} | ||
| isProductASet={isProductASet} | ||
| isProductABundle={isProductABundle} | ||
| comboProduct={comboProduct} | ||
| childProductRefs={childProductRefs} | ||
| childProductSelection={childProductSelection} | ||
| setChildProductSelection={setChildProductSelection} | ||
| childProductOrderability={childProductOrderability} | ||
| setChildProductOrderability={setChildProductOrderability} | ||
| selectedBundleQuantity={selectedBundleQuantity} | ||
| setSelectedBundleQuantity={setSelectedBundleQuantity} | ||
| // Handlers | ||
| handleAddToCart={handleAddToCart} | ||
| handleAddToWishlist={handleAddToWishlist} | ||
| handleProductSetAddToCart={handleProductSetAddToCart} | ||
| handleProductBundleAddToCart={handleProductBundleAddToCart} | ||
| handleChildProductValidation={handleChildProductValidation} | ||
| // Loading states | ||
| isProductLoading={isProductLoading} | ||
| isBasketLoading={isBasketLoading} | ||
| isWishlistLoading={isWishlistLoading} |
There was a problem hiding this comment.
We may complain about the prop drilling here. But I kinda like it.
It reminds me of the old days of getProps, which made it clear of where the data fetching was. And it wasn't spread out across many components.
| product, | ||
| isProductLoading, | ||
| primaryCategory, | ||
| isProductASet, | ||
| isProductABundle, | ||
| comboProduct, | ||
| childProductRefs, | ||
| childProductSelection, | ||
| setChildProductSelection, | ||
| childProductOrderability, | ||
| setChildProductOrderability, | ||
| selectedBundleQuantity, | ||
| setSelectedBundleQuantity, | ||
| handleAddToCart, | ||
| handleAddToWishlist, | ||
| handleProductSetAddToCart, | ||
| handleProductBundleAddToCart, | ||
| handleChildProductValidation, | ||
| isBasketLoading, | ||
| isWishlistLoading | ||
| } = useProductDetailData() |
There was a problem hiding this comment.
This feels like the hook returns a bit too many stuffs. I think we can consider breaking it down into groups that make sense conceptually.
One way how that could work, according to AI:
🧩 Current Hook Analysis
The useProductDetailData hook currently handles 6 major responsibilities and returns 22 different values, making it a "God Hook" that violates the single responsibility principle.
📊 Proposed Conceptual Breakdown
1. Core Product Data Hook (useProductData)
// Handles basic product and category fetching
const useProductData = () => {
// Product fetching (lines 45-68)
// Category fetching (lines 74-80)
// Error handling (lines 115-137)
// Primary category state management (lines 139-148)
// Variant URL management (lines 150-160)
return {
product,
isProductLoading,
primaryCategory,
isProductASet: product?.type.set,
isProductABundle: product?.type.bundle
}
}Used by: All components (Simple and Composite)
2. Bundle/Set State Hook (useBundleSetState)
// Handles complex bundle and set state management
const useBundleSetState = (product) => {
// Child product selection state (lines 81-83)
// Child product orderability state (line 82)
// Bundle quantity state (line 84)
// Bundle children data fetching (lines 89-111)
// Combo product normalization (line 113)
return {
comboProduct,
childProductRefs,
childProductSelection,
setChildProductSelection,
childProductOrderability,
setChildProductOrderability,
selectedBundleQuantity,
setSelectedBundleQuantity
}
}Used by: Only CompositeProductDetails
3. Cart Operations Hook (useProductCartActions)
// Handles all cart-related operations
const useProductCartActions = (product, bundleState) => {
// Basic add to cart (lines 162-209)
// Child product validation (lines 211-240)
// Set add to cart (lines 242-247)
// Bundle add to cart (lines 249-306)
return {
handleAddToCart,
handleProductSetAddToCart,
handleProductBundleAddToCart,
handleChildProductValidation,
isBasketLoading
}
}Used by: All components with different handlers
4. Wishlist Operations (useProductWishlist)
// Already extracted - handles wishlist operations
const useProductWishlist = () => {
return {
handleAddToWishlist,
isWishlistLoading
}
}Used by: All components
🎯 Component Data Requirements Map
SimpleProductDetails needs:
- ✅
useProductData()- Core product info - ✅
useProductCartActions()- Basic cart operations - ✅
useProductWishlist()- Wishlist operations
CompositeProductDetails needs:
- ✅
useProductData()- Core product info - ✅
useBundleSetState()- Complex bundle/set state - ✅
useProductCartActions()- All cart operations - ✅
useProductWishlist()- Wishlist operations
📈 Benefits of This Breakdown
🟢 Reduced Complexity
- Each hook has a single, clear responsibility
- Easier to test individual concerns
- Simpler mental model for developers
🟢 Better Reusability
useProductDatacould be reused on other product pagesuseBundleSetStateonly loaded when needed- Cart operations could be shared across pages
🟢 Improved Performance
- Bundle/set logic only executes for composite products
- Smaller re-render scope when state changes
- Better code splitting opportunities
🟢 Enhanced Maintainability
- Bundle logic isolated from simple product logic
- Each hook can evolve independently
- Clearer debugging when issues arise
🔧 Implementation Strategy
// New main hook that composes smaller hooks
const useProductDetailData = () => {
const productData = useProductData()
const bundleState = useBundleSetState(productData.product)
const cartActions = useProductCartActions(productData.product, bundleState)
const wishlistActions = useProductWishlist()
return {
// Core data (5 values)
...productData,
// Bundle/set data (8 values) - only when needed
...(productData.isProductASet || productData.isProductABundle ? bundleState : {}),
// Actions (5 values)
...cartActions,
...wishlistActions
}
}🚨 Current Pain Points Addressed
- 22 return values → 4 focused hooks with clear boundaries
- 343 lines → ~80 lines per hook with single responsibilities
- Everything coupled → Independent concerns that can be tested/maintained separately
- Bundle logic always loaded → Conditional loading for better performance
This breakdown follows the principle of locality - related concerns stay together, unrelated concerns are separated, and each hook has a clear, focused purpose that matches how the UI components actually consume the data.
There was a problem hiding this comment.
I will follow up on this when i'm back next week, awesome feedback!
| const ProductDetails = (props) => { | ||
| const {isProductASet, isProductABundle} = props | ||
|
|
||
| if (isProductASet || isProductABundle) { | ||
| return <CompositeProductDetails {...props} /> | ||
| } | ||
|
|
||
| return <SimpleProductDetails {...props} /> | ||
| } |
There was a problem hiding this comment.
Minor: how about merging this back into the, um, parent product details component? This isn't much code, so I think it doesn't need to be in its own component.
…actor-pdp @W-18820722 Refactor PDP for better readability and maintainability


Description
This PR refactors the PDP implementation to split the giant index component into separate components and hooks for better maintainability.
Major changes
Note: since this is the first page being refactored, I'd love to keep it simple to not overly optimize it. Once we do the similar work on other pages, we will be able to clearly see the patterns and optimize further.
Types of Changes
How to Test-Drive This PR
Checklists
General
Accessibility Compliance
You must check off all items in one of the follow two lists:
or...
Localization