catalog: migrate remaining 36 useCatalog() consumers to the three filters

Replaces every direct call to the deprecated useCatalog() shim with the
targeted filter(s) (useCatalogData / useCatalogUiState / useCatalogActions).
Each consumer now subscribes only to the slice it actually reads, which
restores React Compiler memoization and stops catalog-wide re-renders
whenever an unrelated key changes.

Removes the now-unused useCatalog shim from useCatalog.ts and the
shim-specific case in tests/useCatalog.filters.test.tsx. The "all four
hooks observe the same singleton" test becomes "all three filters", since
there is no shim left to compare against. useCatalogFavorites swaps its
internal useCatalog() call for useCatalogUiState() (currentType lives in
the UI slice).

Updates CLAUDE.md and docs/ARCHITECTURE.md to reflect that all 48
historical consumers are migrated and the shim is gone.

Vitest: 162/162 (was 163 — minus the deprecated-shim contract case).
This commit is contained in:
simoleo89
2026-05-14 20:05:44 +02:00
parent cb7502f3b0
commit 0f9fa1203b
43 changed files with 123 additions and 137 deletions
+5 -5
View File
@@ -259,15 +259,14 @@ into `configurePreviewServer` so `yarn preview` keeps working.
| `useNitroQuery` + `useNitroEventInvalidator` | `OfferView`, `CatalogLayoutRoomAdsView`, `ModToolsChatlogView`, `CfhChatlogView`, `useGiftConfiguration`, `useUserGroups`, `useClubOffers(windowId)`, `useSellablePetPalette(breed)`, `useMarketplaceConfiguration`, `useClubGifts` (with invalidator) |
| Zustand | `NavigatorRoomCreatorView` (`useRoomCreatorStore`) |
| God-hook split (state + actions + shim) | `doorbell`, `poll`, `furni-chooser`, `user-chooser`, `friend-request`, `chat-input` |
| God-hook split (`useBetween` singleton + state filter + actions filter + shim) | `wired-tools`, `translation`, `notification`, `friends`, `catalog` (three-way: `useCatalogData` / `useCatalogUiState` / `useCatalogActions`, plus the `useCatalog` shim) |
| God-hook split (`useBetween` singleton + state filter + actions filter + shim) | `wired-tools`, `translation`, `notification`, `friends`, `catalog` (three-way: `useCatalogData` / `useCatalogUiState` / `useCatalogActions` — all 48 consumers migrated, deprecated `useCatalog` shim removed) |
| `WidgetErrorBoundary` | `RoomWidgetsView` umbrella |
| Vitest | 163/163 cases — pure helpers + Zustand store + 2 component-/hook-level pilots (WidgetErrorBoundary, useDoorbellState) on top of the renderer-SDK mock at `tests/mocks/renderer-mock.ts`, 34 cases on the catalog pure helpers, 5 contract cases on the catalog filters |
| Vitest | 162/162 cases — pure helpers + Zustand store + 2 component-/hook-level pilots (WidgetErrorBoundary, useDoorbellState) on top of the renderer-SDK mock at `tests/mocks/renderer-mock.ts`, 34 cases on the catalog pure helpers, 4 contract cases on the catalog filters |
| Form Actions | Login / Register / Forgot (LoginView.tsx) |
| Cherry-picked from `duckietm` PR #126 | `UserAccountSettingsView` (reset password / email / username under user settings), plus the wear-badge popup `canShowWearButton` gating |
| Not yet | Notes |
|---|---|
| Migrate the 48 `useCatalog()` consumers to the new filters | The split is done: pure helpers in `useCatalog.helpers.ts`, three filters (`useCatalogData` / `useCatalogUiState` / `useCatalogActions`) plus the deprecated `useCatalog` shim. Three pilot consumers already migrated (`CatalogBuildersClubStatusView`, `CatalogBreadcrumbView`, `CatalogNavigationItemView`). The remaining 45 still hit the shim — incremental work, each migration is mechanical: split the destructure into 2-3 filter calls based on which keys are read. |
| Split `useChatWidget` / `useAvatarInfoWidget` | Both state-driven via events with no clean imperative actions to extract — skip-motivated. Already touched today for the InfoStand listener move. |
| Split `usePetPackageWidget` / `useWordQuizWidget` / `useChatCommandSelector` | Their "actions" mutate internal state or are tightly interdependent — skip-motivated. |
| Hoist Wired Creator Tools shared state to a Zustand slice | Would remove ~25 props passed to the 3 tab sub-components. (Wired-tools split done as singleton-filter; Zustand slice is the next step.) |
@@ -331,8 +330,9 @@ Fix shapes documented; both are reasonable PRs on their own.
`getNodesByOfferIdFromMap`, `getOfferProductKeys`,
`normalizeCatalogType`, `resolveBuilderFurniPlaceableStatus`)
- Catalog three-way filter split: `useCatalogData` /
`useCatalogUiState` / `useCatalogActions` (with the deprecated
`useCatalog` shim) in `src/hooks/catalog/useCatalog.ts`
`useCatalogUiState` / `useCatalogActions` in
`src/hooks/catalog/useCatalog.ts` (all 48 consumers migrated;
deprecated `useCatalog` shim removed)
- Renderer-SDK mock for Vitest: `tests/mocks/renderer-mock.ts`
(aliased over `@nitrots/nitro-renderer` via `vitest.config.mts`).
Hosts the explicit `NitroLogger` mock, the `mockEventDispatcher` /
+6 -10
View File
@@ -502,15 +502,11 @@ filters built on top of the same `useBetween` singleton:
`getBuilderFurniPlaceableStatus`).
The internal store is named `useCatalogStore` and is **not exported**;
the four public entry points (`useCatalogData` / `useCatalogUiState`
/ `useCatalogActions` / `useCatalog`) all funnel into the same
`useBetween` instance, so listeners + state register once. The
deprecated `useCatalog` shim continues to expose the full historical
return shape so the 48 existing consumers compile unchanged; they
should be incrementally migrated to the specific filters as PRs
touch them. Three pilot migrations already landed in
`CatalogBuildersClubStatusView`, `CatalogBreadcrumbView`, and
`CatalogNavigationItemView`.
the three public entry points (`useCatalogData` / `useCatalogUiState`
/ `useCatalogActions`) all funnel into the same `useBetween`
instance, so listeners + state register once. All 48 historical
consumers have been migrated to the targeted filters; the deprecated
`useCatalog` shim has been removed.
Pure helpers in `useCatalog.helpers.ts`:
@@ -569,7 +565,7 @@ empty-map / partial-bucket branches of the offer lookup).
the partial-visible fallback), `buildCatalogNodeTree` (tree
depth + offerId index), and the full decision tree of
`resolveBuilderFurniPlaceableStatus`.
- `useCatalog.filters.test.tsx` (5) — contract tests for the
- `useCatalog.filters.test.tsx` (4) — contract tests for the
three-way singleton-filter split. Stubs `use-between` so the
filters share one fake store, asserts each filter exposes
exactly the keys it owns (no leak across slices), and pins
@@ -1,7 +1,7 @@
import { CatalogAdminCreateOfferComposer, CatalogAdminCreatePageComposer, CatalogAdminDeleteOfferComposer, CatalogAdminDeletePageComposer, CatalogAdminMoveOfferComposer, CatalogAdminMovePageComposer, CatalogAdminPublishComposer, CatalogAdminResultEvent, CatalogAdminSaveOfferComposer, CatalogAdminSavePageComposer } from '@nitrots/nitro-renderer';
import { createContext, FC, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { ICatalogNode, IPurchasableOffer, NotificationAlertType, SendMessageComposer } from '../../api';
import { useCatalog, useMessageEvent, useNotification } from '../../hooks';
import { useCatalogUiState, useMessageEvent, useNotification } from '../../hooks';
export interface IPageEditData
{
@@ -76,7 +76,7 @@ export const useCatalogAdmin = () => useContext(CatalogAdminContext);
export const CatalogAdminProvider: FC<{ children: ReactNode }> = ({ children }) =>
{
const { currentType } = useCatalog();
const { currentType } = useCatalogUiState();
const [ adminMode, setAdminMode ] = useState(false);
const [ editingOffer, setEditingOffer ] = useState<IPurchasableOffer | null>(null);
const [ editingPageData, setEditingPageData ] = useState(false);
@@ -3,7 +3,7 @@ import { FC, useEffect } from 'react';
import { FaCog, FaEdit, FaEye, FaEyeSlash, FaPlus, FaTrash } from 'react-icons/fa';
import { CatalogType, GetConfigurationValue, LocalizeText } from '../../api';
import { Column, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../common';
import { useCatalog } from '../../hooks';
import { useCatalogActions, useCatalogData, useCatalogUiState } from '../../hooks';
import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext';
import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView';
import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView';
@@ -16,7 +16,9 @@ import { MarketplacePostOfferView } from './views/page/layout/marketplace/Market
const CatalogClassicViewInner: FC<{}> = () =>
{
const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null, openCatalogByType = null, toggleCatalogByType = null, currentType = CatalogType.NORMAL } = useCatalog();
const { rootNode = null, currentPage = null, searchResult = null } = useCatalogData();
const { isVisible = false, setIsVisible = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], setSearchResult = null, currentType = CatalogType.NORMAL } = useCatalogUiState();
const { openPageByName = null, openPageByOfferId = null, activateNode = null, openCatalogByType = null, toggleCatalogByType = null } = useCatalogActions();
const catalogAdmin = useCatalogAdmin();
const adminMode = catalogAdmin?.adminMode ?? false;
const setAdminMode = catalogAdmin?.setAdminMode ?? (() =>
+4 -2
View File
@@ -3,7 +3,7 @@ import { FC, useEffect, useState } from 'react';
import { FaCog, FaEdit, FaEye, FaEyeSlash, FaHeart, FaPlus, FaStar, FaTrash } from 'react-icons/fa';
import { CatalogType, LocalizeText } from '../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common';
import { useCatalog, useCatalogFavorites } from '../../hooks';
import { useCatalogActions, useCatalogData, useCatalogFavorites, useCatalogUiState } from '../../hooks';
import { CatalogAdminProvider, useCatalogAdmin } from './CatalogAdminContext';
import { CatalogAdminOfferEditView } from './views/admin/CatalogAdminOfferEditView';
import { CatalogAdminPageEditView } from './views/admin/CatalogAdminPageEditView';
@@ -18,7 +18,9 @@ import { MarketplacePostOfferView } from './views/page/layout/marketplace/Market
const CatalogModernViewInner: FC<{}> = () =>
{
const { isVisible = false, setIsVisible = null, rootNode = null, currentPage = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], searchResult = null, setSearchResult = null, openPageByName = null, openPageByOfferId = null, activateNode = null, openCatalogByType = null, toggleCatalogByType = null, currentType = CatalogType.NORMAL } = useCatalog();
const { rootNode = null, currentPage = null, searchResult = null } = useCatalogData();
const { isVisible = false, setIsVisible = null, navigationHidden = false, setNavigationHidden = null, activeNodes = [], setSearchResult = null, currentType = CatalogType.NORMAL } = useCatalogUiState();
const { openPageByName = null, openPageByOfferId = null, activateNode = null, openCatalogByType = null, toggleCatalogByType = null } = useCatalogActions();
const catalogAdmin = useCatalogAdmin();
const adminMode = catalogAdmin?.adminMode ?? false;
const setAdminMode = catalogAdmin?.setAdminMode ?? (() =>
+2 -2
View File
@@ -1,12 +1,12 @@
import { FC } from 'react';
import { GetConfigurationValue } from '../../api';
import { useCatalog } from '../../hooks';
import { useCatalogData } from '../../hooks';
import { CatalogClassicView } from './CatalogClassicView';
import { CatalogModernView } from './CatalogModernView';
export const CatalogView: FC<{}> = () =>
{
const { catalogLocalizationVersion = 0 } = useCatalog();
const { catalogLocalizationVersion = 0 } = useCatalogData();
const useNewStyle = GetConfigurationValue<boolean>('catalog.style.new', false);
if(useNewStyle) return (
@@ -2,12 +2,12 @@ import { FC, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { FaSave, FaSpinner, FaTimes, FaTrash } from 'react-icons/fa';
import { LocalizeText } from '../../../../api';
import { useCatalog } from '../../../../hooks';
import { useCatalogData } from '../../../../hooks';
import { IOfferEditData, useCatalogAdmin } from '../../CatalogAdminContext';
export const CatalogAdminOfferEditView: FC<{}> = () =>
{
const { currentPage = null } = useCatalog();
const { currentPage = null } = useCatalogData();
const catalogAdmin = useCatalogAdmin();
const editingOffer = catalogAdmin?.editingOffer ?? null;
const setEditingOffer = catalogAdmin?.setEditingOffer;
@@ -1,7 +1,7 @@
import { FC, useEffect, useState } from 'react';
import { FaSave, FaSpinner, FaTimes, FaTrash } from 'react-icons/fa';
import { CatalogType, LocalizeText } from '../../../../api';
import { useCatalog } from '../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../hooks';
import { IPageEditData, useCatalogAdmin } from '../../CatalogAdminContext';
const LAYOUT_OPTIONS = [
@@ -21,7 +21,8 @@ const MODE_OPTIONS = [
export const CatalogAdminPageEditView: FC<{}> = () =>
{
const { currentPage = null, activeNodes = [], rootNode = null, currentType = CatalogType.NORMAL } = useCatalog();
const { currentPage = null, rootNode = null } = useCatalogData();
const { activeNodes = [], currentType = CatalogType.NORMAL } = useCatalogUiState();
const catalogAdmin = useCatalogAdmin();
const editingPageData = catalogAdmin?.editingPageData ?? false;
const editingRootPage = catalogAdmin?.editingRootPage ?? false;
@@ -1,7 +1,7 @@
import { FC, useMemo } from 'react';
import { FaHeart, FaStar, FaTimes } from 'react-icons/fa';
import { ICatalogNode, LocalizeText } from '../../../../api';
import { useCatalog, useCatalogFavorites } from '../../../../hooks';
import { useCatalogActions, useCatalogData, useCatalogFavorites } from '../../../../hooks';
import { CatalogIconView } from '../catalog-icon/CatalogIconView';
interface CatalogFavoritesViewProps
@@ -13,7 +13,8 @@ export const CatalogFavoritesView: FC<CatalogFavoritesViewProps> = props =>
{
const { onClose } = props;
const { favoriteOffers, favoritePageIds, toggleFavoritePage, toggleFavoriteOffer } = useCatalogFavorites();
const { offersToNodes, activateNode, openPageByOfferId, rootNode } = useCatalog();
const { offersToNodes, rootNode } = useCatalogData();
const { activateNode, openPageByOfferId } = useCatalogActions();
const favoritePages = useMemo(() =>
{
@@ -1,6 +1,6 @@
import { FC } from 'react';
import { ICatalogNode } from '../../../../api';
import { useCatalog } from '../../../../hooks';
import { useCatalogData } from '../../../../hooks';
import { CatalogNavigationItemView } from './CatalogNavigationItemView';
import { CatalogNavigationSetView } from './CatalogNavigationSetView';
@@ -12,7 +12,7 @@ export interface CatalogNavigationViewProps
export const CatalogNavigationView: FC<CatalogNavigationViewProps> = props =>
{
const { node = null } = props;
const { searchResult = null } = useCatalog();
const { searchResult = null } = useCatalogData();
return (
<div className="flex flex-col gap-px px-0.5 py-0.5">
@@ -3,7 +3,7 @@ import { FC, MouseEvent, useMemo, useState } from 'react';
import { FaHeart } from 'react-icons/fa';
import { CatalogType, IPurchasableOffer, Offer, ProductTypeEnum } from '../../../../../api';
import { LayoutAvatarImageView, LayoutGridItem, LayoutGridItemProps } from '../../../../../common';
import { useCatalog, useCatalogFavorites, useInventoryFurni } from '../../../../../hooks';
import { useCatalogActions, useCatalogFavorites, useCatalogUiState, useInventoryFurni } from '../../../../../hooks';
interface CatalogGridOfferViewProps extends LayoutGridItemProps
{
@@ -15,7 +15,8 @@ export const CatalogGridOfferView: FC<CatalogGridOfferViewProps> = props =>
{
const { offer = null, selectOffer = null, itemActive = false, ...rest } = props;
const [ isMouseDown, setMouseDown ] = useState(false);
const { requestOfferToMover = null, currentType = CatalogType.NORMAL } = useCatalog();
const { requestOfferToMover = null } = useCatalogActions();
const { currentType = CatalogType.NORMAL } = useCatalogUiState();
const { isVisible = false } = useInventoryFurni();
const { isFavoriteOffer, toggleFavoriteOffer } = useCatalogFavorites();
const isFav = offer ? isFavoriteOffer(offer.offerId) : false;
@@ -2,12 +2,13 @@ import { GetSessionDataManager, IFurnitureData } from '@nitrots/nitro-renderer';
import { FC, useEffect, useState } from 'react';
import { FaSearch, FaTimes } from 'react-icons/fa';
import { CatalogPage, CatalogType, FilterCatalogNode, FurnitureOffer, ICatalogNode, ICatalogPage, IPurchasableOffer, LocalizeText, PageLocalization, SearchResult } from '../../../../../api';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
export const CatalogSearchView: FC<{}> = () =>
{
const [ searchValue, setSearchValue ] = useState('');
const { currentType = null, rootNode = null, searchResult = null, setSearchResult = null, setCurrentPage = null } = useCatalog();
const { rootNode = null, searchResult = null } = useCatalogData();
const { currentType = null, setSearchResult = null, setCurrentPage = null } = useCatalogUiState();
const normalizeSearchText = (value: string) => (value || '')
.toLocaleLowerCase()
@@ -1,7 +1,7 @@
import { FC } from 'react';
import { LocalizeText, SanitizeHtml } from '../../../../../api';
import { Column, Grid, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
import { CatalogBadgeSelectorWidgetView } from '../widgets/CatalogBadgeSelectorWidgetView';
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
@@ -14,7 +14,7 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayoutBadgeDisplayView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const { currentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
return (
<>
@@ -3,7 +3,7 @@ import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api';
import { Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
import { useCatalog, useClubOffers, usePurse, useUiEvent } from '../../../../../hooks';
import { useCatalogData, useClubOffers, usePurse, useUiEvent } from '../../../../../hooks';
import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
import { CatalogLayoutProps } from './CatalogLayout.types';
@@ -14,7 +14,7 @@ export const CatalogLayoutBuildersClubBuyView: FC<CatalogLayoutProps> = () =>
{
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
const { currentPage = null } = useCatalog();
const { currentPage = null } = useCatalogData();
const { getCurrencyAmount = null } = usePurse();
const isPurchasingRef = useRef(false);
const isAddonLayout = (currentPage?.layoutCode === 'builders_club_addons');
@@ -3,7 +3,7 @@ import { FC, useMemo, useState } from 'react';
import { FaFillDrip } from 'react-icons/fa';
import { IPurchasableOffer, SanitizeHtml } from '../../../../../api';
import { AutoGrid, Button, Column, Grid, LayoutGridItem, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
import { CatalogGridOfferView } from '../common/CatalogGridOfferView';
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
import { CatalogLimitedItemWidgetView } from '../widgets/CatalogLimitedItemWidgetView';
@@ -22,7 +22,8 @@ export const CatalogLayoutColorGroupingView: FC<CatalogLayoutColorGroupViewProps
{
const { page = null } = props;
const [ colorableItems, setColorableItems ] = useState<Map<string, number[]>>(new Map<string, number[]>());
const { currentOffer = null, setCurrentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
const { setCurrentOffer = null } = useCatalogUiState();
const [ colorsShowing, setColorsShowing ] = useState<boolean>(false);
const sortByColorIndex = (a: IPurchasableOffer, b: IPurchasableOffer) =>
@@ -2,7 +2,7 @@ import { FC } from 'react';
import { FaEdit, FaPlus } from 'react-icons/fa';
import { GetConfigurationValue, LocalizeText, ProductTypeEnum, SanitizeHtml } from '../../../../../api';
import { Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
import { useCatalogAdmin } from '../../../CatalogAdminContext';
import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
@@ -17,7 +17,7 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayoutDefaultView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const { currentOffer = null, currentPage = null } = useCatalog();
const { currentOffer = null, currentPage = null } = useCatalogData();
const catalogAdmin = useCatalogAdmin();
const adminMode = catalogAdmin?.adminMode ?? false;
@@ -1,7 +1,7 @@
import { FC } from 'react';
import { SanitizeHtml } from '../../../../../api';
import { Column, Grid, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
import { CatalogGuildBadgeWidgetView } from '../widgets/CatalogGuildBadgeWidgetView';
import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView';
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
@@ -13,7 +13,7 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayouGuildCustomFurniView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const { currentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
return (
<Grid>
@@ -1,7 +1,7 @@
import { FC, useState } from 'react';
import { SanitizeHtml } from '../../../../../api';
import { Column, Grid, Text } from '../../../../../common';
import { useCatalog, useUserGroups } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState, useUserGroups } from '../../../../../hooks';
import { CatalogFirstProductSelectorWidgetView } from '../widgets/CatalogFirstProductSelectorWidgetView';
import { CatalogGuildSelectorWidgetView } from '../widgets/CatalogGuildSelectorWidgetView';
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
@@ -12,7 +12,8 @@ export const CatalogLayouGuildForumView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
const { currentOffer = null, setCurrentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
const { setCurrentOffer = null } = useCatalogUiState();
const { data: groups = null } = useUserGroups();
return (
@@ -3,7 +3,7 @@ import { FC, useEffect, useState } from 'react';
import { LocalizeText, SendMessageComposer } from '../../../../../api';
import { useNitroQuery } from '../../../../../api/nitro-query';
import { Button, Column, Text } from '../../../../../common';
import { useCatalog, useNavigator, useRoomPromote } from '../../../../../hooks';
import { useCatalogUiState, useNavigator, useRoomPromote } from '../../../../../hooks';
import { NitroInput } from '../../../../../layout';
import { CatalogLayoutProps } from './CatalogLayout.types';
@@ -18,7 +18,7 @@ export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
const [ extended, setExtended ] = useState<boolean>(false);
const [ categoryId, setCategoryId ] = useState<number>(1);
const { categories = null } = useNavigator();
const { setIsVisible = null } = useCatalog();
const { setIsVisible = null } = useCatalogUiState();
const { promoteInformation, isExtended, setIsExtended } = useRoomPromote();
const { data: availableRooms = [] } = useNitroQuery<RoomAdPurchaseInfoEvent, RoomEntryData[]>({
@@ -2,7 +2,7 @@ import { GetOfficialSongIdMessageComposer, GetSoundManager, MusicPriorities, Off
import { FC, useEffect, useState } from 'react';
import { GetConfigurationValue, LocalizeText, ProductTypeEnum, SanitizeHtml, SendMessageComposer } from '../../../../../api';
import { Button, Column, Grid, LayoutImage, Text } from '../../../../../common';
import { useCatalog, useMessageEvent } from '../../../../../hooks';
import { useCatalogData, useMessageEvent } from '../../../../../hooks';
import { CatalogHeaderView } from '../../catalog-header/CatalogHeaderView';
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
@@ -18,7 +18,7 @@ export const CatalogLayoutSoundMachineView: FC<CatalogLayoutProps> = props =>
const { page = null } = props;
const [ songId, setSongId ] = useState(-1);
const [ officialSongId, setOfficialSongId ] = useState('');
const { currentOffer = null, currentPage = null } = useCatalog();
const { currentOffer = null, currentPage = null } = useCatalogData();
const previewSong = (previewSongId: number) => GetSoundManager().musicController?.playSong(previewSongId, MusicPriorities.PRIORITY_PURCHASE_PREVIEW, 15, 0, 0, 0);
@@ -1,7 +1,7 @@
import { FC, useEffect } from 'react';
import { SanitizeHtml } from '../../../../../api';
import { Column, Grid, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
import { CatalogPurchaseWidgetView } from '../widgets/CatalogPurchaseWidgetView';
import { CatalogSpacesWidgetView } from '../widgets/CatalogSpacesWidgetView';
import { CatalogTotalPriceWidget } from '../widgets/CatalogTotalPriceWidget';
@@ -11,7 +11,7 @@ import { CatalogLayoutProps } from './CatalogLayout.types';
export const CatalogLayoutSpacesView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const { currentOffer = null, roomPreviewer = null } = useCatalog();
const { currentOffer = null, roomPreviewer = null } = useCatalogData();
useEffect(() =>
{
@@ -2,7 +2,7 @@ import { FC, useEffect, useState } from 'react';
import { FaEdit, FaPen, FaPlus, FaTrophy } from 'react-icons/fa';
import { LocalizeText, ProductTypeEnum, SanitizeHtml } from '../../../../../api';
import { Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
import { useCatalogAdmin } from '../../../CatalogAdminContext';
import { CatalogAddOnBadgeWidgetView } from '../widgets/CatalogAddOnBadgeWidgetView';
import { CatalogItemGridWidgetView } from '../widgets/CatalogItemGridWidgetView';
@@ -15,7 +15,8 @@ export const CatalogLayoutTrophiesView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
const [ trophyText, setTrophyText ] = useState<string>('');
const { currentOffer = null, setPurchaseOptions = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
const { setPurchaseOptions = null } = useCatalogUiState();
const catalogAdmin = useCatalogAdmin();
const adminMode = catalogAdmin?.adminMode ?? false;
@@ -3,7 +3,7 @@ import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api';
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
import { useCatalog, useClubOffers, usePurse, useUiEvent } from '../../../../../hooks';
import { useCatalogData, useClubOffers, usePurse, useUiEvent } from '../../../../../hooks';
import { CatalogLayoutProps } from './CatalogLayout.types';
const VIP_WINDOW_ID = 1;
@@ -12,7 +12,7 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
{
const [ pendingOffer, setPendingOffer ] = useState<ClubOfferData>(null);
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
const { currentPage = null } = useCatalog();
const { currentPage = null } = useCatalogData();
const { purse = null, getCurrencyAmount = null } = usePurse();
const { data: offers = null } = useClubOffers(VIP_WINDOW_ID);
const isPurchasingRef = useRef<boolean>(false);
@@ -1,7 +1,7 @@
import { CreateLinkEvent, FrontPageItem } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect } from 'react';
import { Column, Grid } from '../../../../../../common';
import { useCatalog } from '../../../../../../hooks';
import { useCatalogData } from '../../../../../../hooks';
import { CatalogRedeemVoucherView } from '../../common/CatalogRedeemVoucherView';
import { CatalogLayoutProps } from '../CatalogLayout.types';
import { CatalogLayoutFrontPageItemView } from './CatalogLayoutFrontPageItemView';
@@ -9,7 +9,7 @@ import { CatalogLayoutFrontPageItemView } from './CatalogLayoutFrontPageItemView
export const CatalogLayoutFrontpage4View: FC<CatalogLayoutProps> = props =>
{
const { page = null, hideNavigation = null } = props;
const { frontPageItems = [] } = useCatalog();
const { frontPageItems = [] } = useCatalogData();
const selectItem = useCallback((item: FrontPageItem) =>
{
@@ -4,7 +4,7 @@ import { FaCheck, FaEdit, FaFillDrip, FaPaw, FaPlus, FaTimes } from 'react-icons
import { DispatchUiEvent, GetPetAvailableColors, GetPetIndexFromLocalization, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../../api';
import { LayoutGridItem, LayoutPetImageView } from '../../../../../../common';
import { CatalogPurchaseFailureEvent } from '../../../../../../events';
import { useCatalog, useMessageEvent, useSellablePetPalette } from '../../../../../../hooks';
import { useCatalogData, useCatalogUiState, useMessageEvent, useSellablePetPalette } from '../../../../../../hooks';
import { useCatalogAdmin } from '../../../../CatalogAdminContext';
import { CatalogAddOnBadgeWidgetView } from '../../widgets/CatalogAddOnBadgeWidgetView';
import { CatalogTotalPriceWidget } from '../../widgets/CatalogTotalPriceWidget';
@@ -23,7 +23,8 @@ 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, roomPreviewer = null } = useCatalog();
const { currentOffer = null, roomPreviewer = null } = useCatalogData();
const { setCurrentOffer = null, setPurchaseOptions = null } = useCatalogUiState();
const catalogAdmin = useCatalogAdmin();
const adminMode = catalogAdmin?.adminMode ?? false;
const breed: string = (currentOffer?.product?.productData?.type as unknown as string) ?? '';
@@ -1,6 +1,6 @@
import { FC } from 'react';
import { BaseProps, LayoutBadgeImageView } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
interface CatalogAddOnBadgeWidgetViewProps extends BaseProps<HTMLDivElement>
{
@@ -10,7 +10,7 @@ interface CatalogAddOnBadgeWidgetViewProps extends BaseProps<HTMLDivElement>
export const CatalogAddOnBadgeWidgetView: FC<CatalogAddOnBadgeWidgetViewProps> = props =>
{
const { ...rest } = props;
const { currentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
if(!currentOffer || !currentOffer.badgeCode || !currentOffer.badgeCode.length) return null;
@@ -1,7 +1,7 @@
import { StringDataType } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react';
import { AutoGrid, AutoGridProps, LayoutBadgeImageView, LayoutGridItem } from '../../../../../common';
import { useCatalog, useInventoryBadges } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState, useInventoryBadges } from '../../../../../hooks';
const EXCLUDED_BADGE_CODES: string[] = [];
@@ -15,7 +15,8 @@ export const CatalogBadgeSelectorWidgetView: FC<CatalogBadgeSelectorWidgetViewPr
const { columnCount = 5, ...rest } = props;
const [ isVisible, setIsVisible ] = useState(false);
const [ currentBadgeCode, setCurrentBadgeCode ] = useState<string>(null);
const { currentOffer = null, setPurchaseOptions = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
const { setPurchaseOptions = null } = useCatalogUiState();
const { badgeCodes = [], activate = null, deactivate = null } = useInventoryBadges();
const previewStuffData = useMemo(() =>
@@ -1,6 +1,6 @@
import { FC, useEffect, useRef } from 'react';
import { AutoGrid, AutoGridProps, LayoutGridItem } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
interface CatalogBundleGridWidgetViewProps extends AutoGridProps
{
@@ -10,7 +10,7 @@ interface CatalogBundleGridWidgetViewProps extends AutoGridProps
export const CatalogBundleGridWidgetView: FC<CatalogBundleGridWidgetViewProps> = props =>
{
const { columnCount = 5, children = null, ...rest } = props;
const { currentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
const elementRef = useRef<HTMLDivElement>(null);
useEffect(() =>
@@ -1,9 +1,10 @@
import { FC, useEffect } from 'react';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
export const CatalogFirstProductSelectorWidgetView: FC<{}> = props =>
{
const { currentPage = null, setCurrentOffer = null } = useCatalog();
const { currentPage = null } = useCatalogData();
const { setCurrentOffer = null } = useCatalogUiState();
useEffect(() =>
{
@@ -1,7 +1,7 @@
import { StringDataType } from '@nitrots/nitro-renderer';
import { FC, useMemo } from 'react';
import { BaseProps, LayoutBadgeImageView } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
interface CatalogGuildBadgeWidgetViewProps extends BaseProps<HTMLDivElement>
{
@@ -11,7 +11,8 @@ interface CatalogGuildBadgeWidgetViewProps extends BaseProps<HTMLDivElement>
export const CatalogGuildBadgeWidgetView: FC<CatalogGuildBadgeWidgetViewProps> = props =>
{
const { ...rest } = props;
const { currentOffer = null, purchaseOptions = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
const { purchaseOptions = null } = useCatalogUiState();
const { previewStuffData = null } = purchaseOptions;
const badgeCode = useMemo(() =>
@@ -2,12 +2,13 @@ import { StringDataType } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText } from '../../../../../api';
import { Button, Flex } from '../../../../../common';
import { useCatalog, useUserGroups } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState, useUserGroups } from '../../../../../hooks';
export const CatalogGuildSelectorWidgetView: FC<{}> = props =>
{
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState<number>(0);
const { currentOffer = null, setPurchaseOptions = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
const { setPurchaseOptions = null } = useCatalogUiState();
const { data: groups = null } = useUserGroups();
const previewStuffData = useMemo(() =>
@@ -1,7 +1,7 @@
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { IPurchasableOffer } from '../../../../../api';
import { AutoGrid, AutoGridProps } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogActions, useCatalogData } from '../../../../../hooks';
import { useCatalogAdmin } from '../../../CatalogAdminContext';
import { CatalogGridOfferView } from '../common/CatalogGridOfferView';
@@ -13,7 +13,8 @@ interface CatalogItemGridWidgetViewProps extends AutoGridProps
export const CatalogItemGridWidgetView: FC<CatalogItemGridWidgetViewProps> = props =>
{
const { columnCount = 5, children = null, ...rest } = props;
const { currentOffer = null, currentPage = null, selectCatalogOffer = null } = useCatalog();
const { currentOffer = null, currentPage = null } = useCatalogData();
const { selectCatalogOffer = null } = useCatalogActions();
const catalogAdmin = useCatalogAdmin();
const adminMode = catalogAdmin?.adminMode ?? false;
const elementRef = useRef<HTMLDivElement>(null);
@@ -1,11 +1,11 @@
import { FC } from 'react';
import { Offer } from '../../../../../api';
import { LayoutLimitedEditionCompletePlateView } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
export const CatalogLimitedItemWidgetView: FC = props =>
{
const { currentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
if(!currentOffer || (currentOffer.pricingModel !== Offer.PRICING_MODEL_SINGLE) || !currentOffer.product.isUniqueLimitedItem) return null;
@@ -2,7 +2,7 @@ import { FC } from 'react';
import { FaPlus } from 'react-icons/fa';
import { IPurchasableOffer } from '../../../../../api';
import { LayoutCurrencyIcon, Text } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogUiState } from '../../../../../hooks';
interface CatalogPriceDisplayWidgetViewProps
{
@@ -13,7 +13,7 @@ interface CatalogPriceDisplayWidgetViewProps
export const CatalogPriceDisplayWidgetView: FC<CatalogPriceDisplayWidgetViewProps> = props =>
{
const { offer = null, separator = false } = props;
const { purchaseOptions = null } = useCatalog();
const { purchaseOptions = null } = useCatalogUiState();
const { quantity = 1 } = purchaseOptions;
if(!offer) return null;
@@ -3,7 +3,7 @@ import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { BuilderFurniPlaceableStatus, CatalogPurchaseState, CatalogType, DispatchUiEvent, GetClubMemberLevel, LocalStorageKeys, LocalizeText, NotificationBubbleType, Offer, ProductTypeEnum, SendMessageComposer } from '../../../../../api';
import { Button, LayoutLoadingSpinnerView, Text } from '../../../../../common';
import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchaseFailureEvent, CatalogPurchaseNotAllowedEvent, CatalogPurchaseSoldOutEvent, CatalogPurchasedEvent } from '../../../../../events';
import { useCatalog, useLocalStorage, useNotification, usePurse, useUiEvent } from '../../../../../hooks';
import { useCatalogActions, useCatalogData, useCatalogUiState, useLocalStorage, useNotification, usePurse, useUiEvent } from '../../../../../hooks';
interface CatalogPurchaseWidgetViewProps
{
@@ -20,7 +20,9 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
const [ purchaseWillBeGift, setPurchaseWillBeGift ] = useState(false);
const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE);
const [ catalogSkipPurchaseConfirmation, setCatalogSkipPurchaseConfirmation ] = useLocalStorage(LocalStorageKeys.CATALOG_SKIP_PURCHASE_CONFIRMATION, false);
const { currentOffer = null, currentPage = null, currentType = CatalogType.NORMAL, purchaseOptions = null, setPurchaseOptions = null, requestOfferToMover = null, setCatalogPlaceMultipleObjects = null, getBuilderFurniPlaceableStatus = null } = useCatalog();
const { currentOffer = null, currentPage = null } = useCatalogData();
const { currentType = CatalogType.NORMAL, purchaseOptions = null, setPurchaseOptions = null, setCatalogPlaceMultipleObjects = null } = useCatalogUiState();
const { requestOfferToMover = null, getBuilderFurniPlaceableStatus = null } = useCatalogActions();
const { getCurrencyAmount = null } = usePurse();
const { showSingleBubble = null } = useNotification();
@@ -1,10 +1,10 @@
import { FC } from 'react';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
import { CatalogPriceDisplayWidgetView } from './CatalogPriceDisplayWidgetView';
export const CatalogSimplePriceWidgetView: FC<{}> = props =>
{
const { currentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
return (
<div className="flex items-center bg-muted p-1 rounded gap-1">
@@ -1,7 +1,7 @@
import { FC, useEffect, useRef, useState } from 'react';
import { IPurchasableOffer, LocalizeText, Offer, ProductTypeEnum } from '../../../../../api';
import { AutoGrid, AutoGridProps, Button } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
import { CatalogGridOfferView } from '../common/CatalogGridOfferView';
interface CatalogSpacesWidgetViewProps extends AutoGridProps
@@ -17,7 +17,8 @@ export const CatalogSpacesWidgetView: FC<CatalogSpacesWidgetViewProps> = props =
const [ groupedOffers, setGroupedOffers ] = useState<IPurchasableOffer[][]>(null);
const [ selectedGroupIndex, setSelectedGroupIndex ] = useState(-1);
const [ selectedOfferForGroup, setSelectedOfferForGroup ] = useState<IPurchasableOffer[]>(null);
const { currentPage = null, currentOffer = null, setCurrentOffer = null, setPurchaseOptions = null } = useCatalog();
const { currentPage = null, currentOffer = null } = useCatalogData();
const { setCurrentOffer = null, setPurchaseOptions = null } = useCatalogUiState();
const elementRef = useRef<HTMLDivElement>(null);
const setSelectedOffer = (offer: IPurchasableOffer) =>
@@ -1,14 +1,15 @@
import { FC } from 'react';
import { FaMinus, FaPlus } from 'react-icons/fa';
import { LocalizeText } from '../../../../../api';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
const MIN_VALUE: number = 1;
const MAX_VALUE: number = 99;
export const CatalogSpinnerWidgetView: FC<{}> = props =>
{
const { currentOffer = null, purchaseOptions = null, setPurchaseOptions = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
const { purchaseOptions = null, setPurchaseOptions = null } = useCatalogUiState();
const { quantity = 1 } = purchaseOptions;
const updateQuantity = (value: number) =>
@@ -1,6 +1,6 @@
import { FC } from 'react';
import { Column, ColumnProps } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData } from '../../../../../hooks';
import { CatalogPriceDisplayWidgetView } from './CatalogPriceDisplayWidgetView';
interface CatalogSimplePriceWidgetViewProps extends ColumnProps
@@ -10,7 +10,7 @@ interface CatalogSimplePriceWidgetViewProps extends ColumnProps
export const CatalogTotalPriceWidget: FC<CatalogSimplePriceWidgetViewProps> = props =>
{
const { gap = 1, ...rest } = props;
const { currentOffer = null } = useCatalog();
const { currentOffer = null } = useCatalogData();
return (
<Column gap={ gap } { ...rest }>
@@ -2,11 +2,12 @@ import { GetAvatarRenderManager, GetSessionDataManager, Vector3d } from '@nitrot
import { FC, useEffect } from 'react';
import { BuildPurchasableClothingFigure, FurniCategory, Offer, ProductTypeEnum } from '../../../../../api';
import { AutoGrid, Column, LayoutGridItem, LayoutRoomPreviewerView } from '../../../../../common';
import { useCatalog } from '../../../../../hooks';
import { useCatalogData, useCatalogUiState } from '../../../../../hooks';
export const CatalogViewProductWidgetView: FC<{}> = props =>
{
const { currentOffer = null, roomPreviewer = null, purchaseOptions = null } = useCatalog();
const { currentOffer = null, roomPreviewer = null } = useCatalogData();
const { purchaseOptions = null } = useCatalogUiState();
const { previewStuffData = null } = purchaseOptions;
useEffect(() =>
-8
View File
@@ -1060,11 +1060,3 @@ export const useCatalogActions = () =>
};
};
/**
* Deprecated. Kept so the 48 existing consumers compile unchanged
* incrementally migrate them to `useCatalogData` / `useCatalogUiState`
* / `useCatalogActions` and remove this shim once the call sites are
* gone. Mirrors the same `useBetween` singleton, so behavior is
* identical.
*/
export const useCatalog = () => useBetween(useCatalogStore);
+2 -2
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { useBetween } from 'use-between';
import { CatalogType } from '../../api';
import { useCatalog } from './useCatalog';
import { useCatalogUiState } from './useCatalog';
import { getOffersStorageKey, getPagesStorageKey, IFavoriteOffer, LEGACY_STORAGE_KEY_OFFERS, LEGACY_STORAGE_KEY_PAGES, normalizeCatalogType, parseOffers, parsePages } from './useCatalogFavorites.helpers';
export type { IFavoriteOffer } from './useCatalogFavorites.helpers';
@@ -66,7 +66,7 @@ const writePages = (catalogType: string, ids: number[]) =>
const useCatalogFavoritesState = () =>
{
const { currentType = CatalogType.NORMAL } = useCatalog();
const { currentType = CatalogType.NORMAL } = useCatalogUiState();
const catalogType = normalizeCatalogType(currentType);
const [ favoriteOffersByType, setFavoriteOffersByType ] = useState<Record<string, IFavoriteOffer[]>>({
[CatalogType.NORMAL]: [],
+12 -36
View File
@@ -75,7 +75,7 @@ vi.mock('use-between', () => ({
// Import AFTER the mock is set up. The hooks resolve `useBetween` at
// import time via the module graph, so the order matters.
import { useCatalog, useCatalogActions, useCatalogData, useCatalogUiState } from '../src/hooks/catalog/useCatalog';
import { useCatalogActions, useCatalogData, useCatalogUiState } from '../src/hooks/catalog/useCatalog';
describe('useCatalog filter contract', () =>
{
@@ -160,47 +160,23 @@ describe('useCatalog filter contract', () =>
expect(result.current.openCatalogByType).toBe(fakeStore.openCatalogByType);
});
it('all four hooks observe the same singleton — refs are ===', () =>
it('all three filters observe the same singleton — refs are ===', () =>
{
const { result } = renderHook(() =>
({
data: useCatalogData(),
ui: useCatalogUiState(),
actions: useCatalogActions(),
full: useCatalog()
actions: useCatalogActions()
}));
// The shim and the slices reach the same fakeStore. Any
// accidental copy would break this `===` check.
expect(result.current.full.activateNode).toBe(result.current.actions.activateNode);
expect(result.current.full.openCatalogByType).toBe(result.current.actions.openCatalogByType);
expect(result.current.full.setIsVisible).toBe(result.current.ui.setIsVisible);
expect(result.current.full.setCurrentPage).toBe(result.current.ui.setCurrentPage);
expect(result.current.full.rootNode).toBe(result.current.data.rootNode);
expect(result.current.full.furniCount).toBe(result.current.data.furniCount);
expect(result.current.full.roomPreviewer).toBe(result.current.data.roomPreviewer);
});
it('useCatalog (deprecated shim) preserves the full historical surface', () =>
{
const { result } = renderHook(() => useCatalog());
// Sample one field from each slice, including the setters
// that the 48 existing consumers still destructure straight
// out of `useCatalog()`. If a setter or callback ever stops
// being forwarded, the shim breaks and those consumers
// silently fail.
const required = [
'rootNode', 'offersToNodes', 'currentPage', 'currentOffer', 'frontPageItems',
'isVisible', 'setIsVisible', 'pageId', 'previousPageId', 'currentType',
'setCurrentPage', 'setCurrentOffer', 'setSearchResult',
'openCatalogByType', 'toggleCatalogByType', 'activateNode',
'openPageById', 'openPageByName', 'openPageByOfferId',
'requestOfferToMover', 'selectCatalogOffer',
'getNodeById', 'getNodeByName', 'getBuilderFurniPlaceableStatus',
'furniCount', 'furniLimit', 'secondsLeft', 'updateTime'
];
for(const key of required) expect(result.current).toHaveProperty(key);
// Each slice reaches the same fakeStore via useBetween. Any
// accidental copy would break these `===` checks.
expect(result.current.actions.activateNode).toBe(fakeStore.activateNode);
expect(result.current.actions.openCatalogByType).toBe(fakeStore.openCatalogByType);
expect(result.current.ui.setIsVisible).toBe(fakeStore.setIsVisible);
expect(result.current.ui.setCurrentPage).toBe(fakeStore.setCurrentPage);
expect(result.current.data.rootNode).toBe(fakeStore.rootNode);
expect(result.current.data.furniCount).toBe(fakeStore.furniCount);
expect(result.current.data.roomPreviewer).toBe(fakeStore.roomPreviewer);
});
});