fix(cart): pad RelatedProducts to 3 cards even on first pick

The catalog only has 4 products; with 2 already in the cart, the
initial pick's pool shrinks to 2 and pickRandom's slice can't pad
past what's available, so the grid rendered 2 cards instead of 3.
The swap-after-add path already had fallback logic for this exact
case — extracted it into pickWithFallback() and reused it for the
initial pick too.
This commit is contained in:
Marco
2026-07-19 23:22:26 +00:00
parent d472eb546f
commit 0c72b70de7
+23 -19
View File
@@ -16,6 +16,27 @@ function pickRandom(allIds: string[], excludeIds: string[], count: number): stri
return shuffled.slice(0, count);
}
// The catalog only has a handful of products — once the cart holds enough
// distinct ones, "N recommendations that aren't already in the cart" can
// become impossible (e.g. 4 products total, 2 already in the cart, but
// DISPLAY_COUNT is 3 — only 2 non-cart products exist, period). Falling
// back to re-suggesting something already in the cart (a normal "grab
// another one" pattern) beats silently shrinking the grid below
// DISPLAY_COUNT. Shared by both the initial pick and the swap-after-add
// path so neither can under-fill the grid.
function pickWithFallback(allIds: string[], excludeIds: string[], keep: string[], count: number): string[] {
const missing = count - keep.length;
if (missing <= 0) return keep;
let picks = pickRandom(allIds, [...excludeIds, ...keep], missing);
if (picks.length < missing) {
const stillMissing = missing - picks.length;
const fallback = pickRandom(allIds, [...keep, ...picks], stillMissing);
picks = [...picks, ...fallback];
}
return [...keep, ...picks];
}
export function RelatedProducts() {
const cart = useCart();
const products = useProducts();
@@ -55,7 +76,7 @@ export function RelatedProducts() {
if (!pickedRef.current) {
pickedRef.current = true;
setDisplayIds(pickRandom(productIds, cartIds, DISPLAY_COUNT));
setDisplayIds(pickWithFallback(productIds, cartIds, [], DISPLAY_COUNT));
return;
}
@@ -67,24 +88,7 @@ export function RelatedProducts() {
swapTimeoutRef.current = setTimeout(() => {
setDisplayIds((prev) => {
const stillRelevant = prev.filter((id) => !cartIds.includes(id));
const missing = DISPLAY_COUNT - stillRelevant.length;
if (missing <= 0) return stillRelevant;
let replacements = pickRandom(productIds, [...cartIds, ...stillRelevant], missing);
// The catalog only has a handful of products — once the cart
// holds enough distinct ones, "N recommendations that aren't
// already in the cart" can become impossible. Falling back to
// re-suggesting something already in the cart (a normal "grab
// another one" pattern) beats silently shrinking the grid below
// DISPLAY_COUNT.
if (replacements.length < missing) {
const stillMissing = missing - replacements.length;
const fallback = pickRandom(productIds, [...stillRelevant, ...replacements], stillMissing);
replacements = [...replacements, ...fallback];
}
return [...stillRelevant, ...replacements];
return pickWithFallback(productIds, cartIds, stillRelevant, DISPLAY_COUNT);
});
}, FEEDBACK_MS);
}, [cartKey, productIds]);