mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
fix(catalog): stabilise hook order in CatalogPurchaseWidgetView
React reported "Rendered more hooks than during the previous render" when CatalogPurchaseWidgetView transitioned from currentOffer=null to a real offer: hook count jumped from 22 to 23 because the useMemo/useEffect block for the builders-club placement state sat *below* the `if(!currentOffer) return null` early-return on line 140. On the first render it never ran; on the next render (offer loaded) it did, and React's hook-call tracker flagged the divergence and unmounted the component via the error boundary. Fix: move the three builders-club hooks (useMemo builderPlaceableStatus, useMemo buildersClubPlaceOneButtonStyle, useEffect interval) above the early return. They already short-circuit cleanly when isBuildersClubPlaceable is false — added a defensive `!currentOffer` guard on the first useMemo and an explicit `!!currentOffer` clause on the derived isBuildersClubPlaceable so the .product access stays safe when offer is null. Behavior unchanged for the loaded-offer path; the early-render path now runs the hooks but their bodies no-op. Verification: yarn typecheck clean, yarn test 209/209.
This commit is contained in:
@@ -137,15 +137,19 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
|
|||||||
};
|
};
|
||||||
}, [ purchaseState ]);
|
}, [ purchaseState ]);
|
||||||
|
|
||||||
if(!currentOffer) return null;
|
// Builders-club state — derived + hooks MUST run unconditionally on
|
||||||
|
// every render so the hook order stays stable even when currentOffer
|
||||||
|
// is null (the `if(!currentOffer) return null` below would otherwise
|
||||||
|
// hide the useMemo/useEffect block from the first render and React
|
||||||
|
// would flag "Rendered more hooks than during the previous render").
|
||||||
const isBuildersClubOffer = (currentType === CatalogType.BUILDER);
|
const isBuildersClubOffer = (currentType === CatalogType.BUILDER);
|
||||||
const isBuildersClubPlaceable = isBuildersClubOffer
|
const isBuildersClubPlaceable = isBuildersClubOffer
|
||||||
|
&& !!currentOffer
|
||||||
&& !!currentOffer.product
|
&& !!currentOffer.product
|
||||||
&& ((currentOffer.product.productType === ProductTypeEnum.FLOOR) || (currentOffer.product.productType === ProductTypeEnum.WALL));
|
&& ((currentOffer.product.productType === ProductTypeEnum.FLOOR) || (currentOffer.product.productType === ProductTypeEnum.WALL));
|
||||||
const builderPlaceableStatus = useMemo(() =>
|
const builderPlaceableStatus = useMemo(() =>
|
||||||
{
|
{
|
||||||
if(!isBuildersClubPlaceable || !getBuilderFurniPlaceableStatus) return BuilderFurniPlaceableStatus.OKAY;
|
if(!isBuildersClubPlaceable || !getBuilderFurniPlaceableStatus || !currentOffer) return BuilderFurniPlaceableStatus.OKAY;
|
||||||
|
|
||||||
return getBuilderFurniPlaceableStatus(currentOffer);
|
return getBuilderFurniPlaceableStatus(currentOffer);
|
||||||
}, [ currentOffer, getBuilderFurniPlaceableStatus, isBuildersClubPlaceable, builderPlaceableRefreshTick ]);
|
}, [ currentOffer, getBuilderFurniPlaceableStatus, isBuildersClubPlaceable, builderPlaceableRefreshTick ]);
|
||||||
@@ -164,6 +168,8 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [ isBuildersClubPlaceable ]);
|
}, [ isBuildersClubPlaceable ]);
|
||||||
|
|
||||||
|
if(!currentOffer) return null;
|
||||||
|
|
||||||
const PurchaseButton = () =>
|
const PurchaseButton = () =>
|
||||||
{
|
{
|
||||||
if(isBuildersClubPlaceable)
|
if(isBuildersClubPlaceable)
|
||||||
|
|||||||
Reference in New Issue
Block a user