useSellablePetPalette(breed): per-breed TanStack query for pet picker

CatalogLayoutPetView previously read 'catalogOptions.petPalettes' (an
accumulating array of CatalogPetPalette objects keyed by breed) and,
on cache miss, dispatched GetSellablePetPalettesComposer(productData.type)
inline. useCatalog kept the matching SellablePetPalettesMessageEvent
listener that appended each new breed to the array (deduping by breed
identity).

Migrate the request/response pair to a TanStack query parameterized on
breed:

  useSellablePetPalette(breed)
    key:     ['nitro', 'catalog', 'petPalette', breed]
    request: () => new GetSellablePetPalettesComposer(breed)
    parser:  SellablePetPalettesMessageEvent
    accept:  event.getParser().productCode === breed
    select:  build a CatalogPetPalette from parser
    enabled: !!breed (avoid spamming composers before currentOffer is set)
    staleTime: Infinity

The view now derives breed from currentOffer.product.productData.type
and reads 'const { data: petPalette }'. The cache-miss-then-fetch
two-pass effect collapses into a single effect that runs once
petPalette resolves (or clears state when offer/petPalette aren't
ready).

Drops the matching listener from useCatalog, drops petPalettes from
ICatalogOptions, and removes the now-unused CatalogPetPalette /
SellablePetPalettesMessageEvent imports from useCatalog.
This commit is contained in:
simoleo89
2026-05-11 22:28:55 +02:00
parent 2a5b9a4a98
commit 3947781495
5 changed files with 55 additions and 65 deletions
-2
View File
@@ -1,9 +1,7 @@
import { ClubGiftInfoParser, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer';
import { CatalogPetPalette } from './CatalogPetPalette';
export interface ICatalogOptions
{
petPalettes?: CatalogPetPalette[];
clubGifts?: ClubGiftInfoParser;
marketplaceConfiguration?: MarketplaceConfigurationMessageParser;
}
@@ -1,10 +1,10 @@
import { ApproveNameMessageComposer, ApproveNameMessageEvent, ColorConverter, GetSellablePetPalettesComposer, PurchaseFromCatalogComposer, SellablePetPaletteData } from '@nitrots/nitro-renderer';
import { ApproveNameMessageComposer, ApproveNameMessageEvent, ColorConverter, PurchaseFromCatalogComposer, SellablePetPaletteData } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FaCheck, FaEdit, FaFillDrip, FaPaw, FaPlus, FaTimes } from 'react-icons/fa';
import { DispatchUiEvent, GetPetAvailableColors, GetPetIndexFromLocalization, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../../api';
import { LayoutGridItem, LayoutPetImageView } from '../../../../../../common';
import { CatalogPurchaseFailureEvent } from '../../../../../../events';
import { useCatalog, useMessageEvent } from '../../../../../../hooks';
import { useCatalog, useMessageEvent, useSellablePetPalette } from '../../../../../../hooks';
import { useCatalogAdmin } from '../../../../CatalogAdminContext';
import { CatalogAddOnBadgeWidgetView } from '../../widgets/CatalogAddOnBadgeWidgetView';
import { CatalogTotalPriceWidget } from '../../widgets/CatalogTotalPriceWidget';
@@ -23,10 +23,11 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
const [ petName, setPetName ] = useState('');
const [ approvalPending, setApprovalPending ] = useState(true);
const [ approvalResult, setApprovalResult ] = useState(-1);
const { currentOffer = null, setCurrentOffer = null, setPurchaseOptions = null, catalogOptions = null, roomPreviewer = null } = useCatalog();
const { currentOffer = null, setCurrentOffer = null, setPurchaseOptions = null, roomPreviewer = null } = useCatalog();
const catalogAdmin = useCatalogAdmin();
const adminMode = catalogAdmin?.adminMode ?? false;
const { petPalettes = null } = catalogOptions;
const breed: string = (currentOffer?.product?.productData?.type as unknown as string) ?? '';
const { data: petPalette = null } = useSellablePetPalette(breed);
const getColor = useMemo(() =>
{
@@ -129,39 +130,25 @@ export const CatalogLayoutPetView: FC<CatalogLayoutProps> = props =>
useEffect(() =>
{
if(!currentOffer) return;
const productData = currentOffer.product.productData;
if(!productData) return;
if(petPalettes)
if(!currentOffer || !petPalette)
{
for(const paletteData of petPalettes)
{
if(paletteData.breed !== productData.type) continue;
const palettes: SellablePetPaletteData[] = [];
for(const palette of paletteData.palettes)
{
if(!palette.sellable) continue;
palettes.push(palette);
}
setSelectedPaletteIndex((palettes.length ? 0 : -1));
setSellablePalettes(palettes);
return;
}
setSelectedPaletteIndex(-1);
setSellablePalettes([]);
return;
}
setSelectedPaletteIndex(-1);
setSellablePalettes([]);
const palettes: SellablePetPaletteData[] = [];
SendMessageComposer(new GetSellablePetPalettesComposer(productData.type));
}, [ currentOffer, petPalettes ]);
for(const palette of petPalette.palettes)
{
if(!palette.sellable) continue;
palettes.push(palette);
}
setSelectedPaletteIndex(palettes.length ? 0 : -1);
setSellablePalettes(palettes);
}, [ currentOffer, petPalette ]);
useEffect(() =>
{
+1
View File
@@ -4,3 +4,4 @@ export * from './useCatalogPlaceMultipleItems';
export * from './useCatalogSkipPurchaseConfirmation';
export * from './useClubOffers';
export * from './useGiftConfiguration';
export * from './useSellablePetPalette';
+2 -30
View File
@@ -1,7 +1,7 @@
import { BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPublishedMessageEvent, ClubGiftInfoEvent, CreateLinkEvent, FrontPageItem, FurniturePlaceComposer, FurniturePlacePaintComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetClubGiftInfo, GetRoomEngine, GetSessionDataManager, GetTickerTime, LegacyDataType, LimitedEditionSoldOutEvent, MarketplaceMakeOfferResult, NodeData, ProductOfferEvent, PurchaseErrorMessageEvent, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, RoomControllerLevel, RoomEngineObjectPlacedEvent, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType, RoomObjectVariable, RoomPreviewer, SellablePetPalettesMessageEvent, Vector3d } from '@nitrots/nitro-renderer';
import { BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPublishedMessageEvent, ClubGiftInfoEvent, CreateLinkEvent, FrontPageItem, FurniturePlaceComposer, FurniturePlacePaintComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetClubGiftInfo, GetRoomEngine, GetSessionDataManager, GetTickerTime, LegacyDataType, LimitedEditionSoldOutEvent, MarketplaceMakeOfferResult, NodeData, ProductOfferEvent, PurchaseErrorMessageEvent, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, RoomControllerLevel, RoomEngineObjectPlacedEvent, RoomObjectCategory, RoomObjectPlacementSource, RoomObjectType, RoomObjectVariable, RoomPreviewer, Vector3d } from '@nitrots/nitro-renderer';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useBetween } from 'use-between';
import { BuilderFurniPlaceableStatus, CatalogNode, CatalogPage, CatalogPetPalette, CatalogType, DispatchUiEvent, FurniCategory, GetFurnitureData, GetProductDataForLocalization, GetRoomSession, ICatalogNode, ICatalogOptions, ICatalogPage, IPageLocalization, IProduct, IPurchasableOffer, IPurchaseOptions, LocalizeText, NotificationAlertType, Offer, PageLocalization, PlacedObjectPurchaseData, PlaySound, Product, ProductTypeEnum, RequestedPage, SearchResult, SendMessageComposer, SoundNames } from '../../api';
import { BuilderFurniPlaceableStatus, CatalogNode, CatalogPage, CatalogType, DispatchUiEvent, FurniCategory, GetFurnitureData, GetProductDataForLocalization, GetRoomSession, ICatalogNode, ICatalogOptions, ICatalogPage, IPageLocalization, IProduct, IPurchasableOffer, IPurchaseOptions, LocalizeText, NotificationAlertType, Offer, PageLocalization, PlacedObjectPurchaseData, PlaySound, Product, ProductTypeEnum, RequestedPage, SearchResult, SendMessageComposer, SoundNames } from '../../api';
import { CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogPurchaseSoldOutEvent, CatalogPurchasedEvent, InventoryFurniAddedEvent } from '../../events';
import { useMessageEvent, useNitroEvent, useUiEvent } from '../events';
import { useNotification } from '../notification';
@@ -710,34 +710,6 @@ const useCatalogState = () =>
// (this._isObjectMoverRequested) && (this._purchasableOffer)
});
useMessageEvent<SellablePetPalettesMessageEvent>(SellablePetPalettesMessageEvent, event =>
{
const parser = event.getParser();
const petPalette = new CatalogPetPalette(parser.productCode, parser.palettes.slice());
setCatalogOptions(prevValue =>
{
const petPalettes = [];
if(prevValue.petPalettes) petPalettes.push(...prevValue.petPalettes);
for(let i = 0; i < petPalettes.length; i++)
{
const palette = petPalettes[i];
if(palette.breed === petPalette.breed)
{
petPalettes.splice(i, 1);
break;
}
}
petPalettes.push(petPalette);
return { ...prevValue, petPalettes };
});
});
@@ -0,0 +1,32 @@
import { GetSellablePetPalettesComposer, SellablePetPalettesMessageEvent } from '@nitrots/nitro-renderer';
import { UseQueryResult } from '@tanstack/react-query';
import { CatalogPetPalette } from '../../api';
import { useNitroQuery } from '../../api/nitro-query';
/**
* Sellable palettes for a given pet breed, as returned by
* GetSellablePetPalettesComposer(breed) → SellablePetPalettesMessageEvent.
* The renderer multiplexes one event type for every breed; accept()
* keeps each query slot listening only for the matching productCode.
*
* Replaces the per-breed accumulator that previously lived in
* useCatalog (writing to catalogOptions.petPalettes). The catalog pet
* page now reads via `useSellablePetPalette(productData.type)`.
*
* The breed identifier is the localization product code string
* (e.g. 'pet_egg', 'pet_dog', ...). Disabled while breed is empty so
* we don't spam composers at mount before the offer is known.
*/
export const useSellablePetPalette = (
breed: string,
options: { enabled?: boolean } = {}
): UseQueryResult<CatalogPetPalette> =>
useNitroQuery<SellablePetPalettesMessageEvent, CatalogPetPalette>({
key: [ 'nitro', 'catalog', 'petPalette', breed ],
request: () => new GetSellablePetPalettesComposer(breed),
parser: SellablePetPalettesMessageEvent,
accept: event => (event.getParser().productCode === breed),
select: event => new CatalogPetPalette(event.getParser().productCode, event.getParser().palettes.slice()),
enabled: (options.enabled ?? true) && !!breed,
staleTime: Infinity
});