From 394778149556a368c69987d7b76134c3e77f0a42 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 22:28:55 +0200 Subject: [PATCH] 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. --- src/api/catalog/ICatalogOptions.ts | 2 - .../page/layout/pets/CatalogLayoutPetView.tsx | 53 +++++++------------ src/hooks/catalog/index.ts | 1 + src/hooks/catalog/useCatalog.ts | 32 +---------- src/hooks/catalog/useSellablePetPalette.ts | 32 +++++++++++ 5 files changed, 55 insertions(+), 65 deletions(-) create mode 100644 src/hooks/catalog/useSellablePetPalette.ts diff --git a/src/api/catalog/ICatalogOptions.ts b/src/api/catalog/ICatalogOptions.ts index 4842d97..e50559c 100644 --- a/src/api/catalog/ICatalogOptions.ts +++ b/src/api/catalog/ICatalogOptions.ts @@ -1,9 +1,7 @@ import { ClubGiftInfoParser, MarketplaceConfigurationMessageParser } from '@nitrots/nitro-renderer'; -import { CatalogPetPalette } from './CatalogPetPalette'; export interface ICatalogOptions { - petPalettes?: CatalogPetPalette[]; clubGifts?: ClubGiftInfoParser; marketplaceConfiguration?: MarketplaceConfigurationMessageParser; } diff --git a/src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx b/src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx index 08a9356..589d6bb 100644 --- a/src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx +++ b/src/components/catalog/views/page/layout/pets/CatalogLayoutPetView.tsx @@ -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 = 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 = 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(() => { diff --git a/src/hooks/catalog/index.ts b/src/hooks/catalog/index.ts index 288ef99..2995cd8 100644 --- a/src/hooks/catalog/index.ts +++ b/src/hooks/catalog/index.ts @@ -4,3 +4,4 @@ export * from './useCatalogPlaceMultipleItems'; export * from './useCatalogSkipPurchaseConfirmation'; export * from './useClubOffers'; export * from './useGiftConfiguration'; +export * from './useSellablePetPalette'; diff --git a/src/hooks/catalog/useCatalog.ts b/src/hooks/catalog/useCatalog.ts index a68c36e..b21a39a 100644 --- a/src/hooks/catalog/useCatalog.ts +++ b/src/hooks/catalog/useCatalog.ts @@ -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, 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 }; - }); - }); diff --git a/src/hooks/catalog/useSellablePetPalette.ts b/src/hooks/catalog/useSellablePetPalette.ts new file mode 100644 index 0000000..700cffc --- /dev/null +++ b/src/hooks/catalog/useSellablePetPalette.ts @@ -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 => + useNitroQuery({ + 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 + });