refactor(navigator): remove search ownership from useNavigatorStore

P2 core surgery: search result + NavigatorSearchEvent listener +
sendSearch + reloadCurrentSearch all leave useNavigatorStore. The new
useNavigatorSearch query hook owns the cache. useNavigatorActions is
deleted entirely — the only two actions it exposed are gone, and no
consumer outside Navigator depended on it.

NavigatorMetadataEvent handler now seeds the UI store's currentTabCode
on first arrival, activating the query the moment top-level contexts
land.

useNavigatorData: searchResult removed from closure and return.
useNavigatorUiState: currentTabCode + currentFilter added.
index.ts: useNavigatorActions removed, useNavigatorSearch added.

NavigatorView.tsx is intentionally broken at this commit and gets
fixed in the next.
This commit is contained in:
simoleo89
2026-05-27 19:20:27 +02:00
parent 7435326dad
commit ee3736474d
6 changed files with 18 additions and 90 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
export { useNavigatorActions } from './useNavigatorActions';
export { useNavigatorData } from './useNavigatorData';
export { useNavigatorSearch } from './useNavigatorSearch';
export { useNavigatorUiState } from './useNavigatorUiState';
export { useNavigatorUiStore } from './navigatorUiStore';
export { useDoorState } from '../rooms/widgets/useDoorState';
@@ -1,8 +0,0 @@
import { useBetween } from 'use-between';
import { useNavigatorStore } from './useNavigatorStore';
export const useNavigatorActions = () =>
{
const { sendSearch, reloadCurrentSearch } = useBetween(useNavigatorStore);
return { sendSearch, reloadCurrentSearch };
};
+2 -2
View File
@@ -6,12 +6,12 @@ export const useNavigatorData = () =>
const {
categories, eventCategories, favouriteRoomIds,
topLevelContext, topLevelContexts,
searchResult, navigatorSearches, navigatorData
navigatorSearches, navigatorData
} = useBetween(useNavigatorStore);
return {
categories, eventCategories, favouriteRoomIds,
topLevelContext, topLevelContexts,
searchResult, navigatorSearches, navigatorData
navigatorSearches, navigatorData
};
};
+4 -10
View File
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { useNavigatorActions, useNavigatorData, useNavigatorUiState } from './index';
import { useNavigatorData, useNavigatorUiState } from './index';
describe('navigator filter shapes (smoke)', () =>
{
@@ -10,24 +10,18 @@ describe('navigator filter shapes (smoke)', () =>
expect(Object.keys(result.current).sort()).toEqual([
'categories', 'eventCategories', 'favouriteRoomIds',
'navigatorData', 'navigatorSearches',
'searchResult', 'topLevelContext', 'topLevelContexts'
'topLevelContext', 'topLevelContexts'
].sort());
});
it('useNavigatorUiState returns the 9 documented flags', () =>
it('useNavigatorUiState returns the 11 documented flags', () =>
{
const { result } = renderHook(() => useNavigatorUiState());
expect(Object.keys(result.current).sort()).toEqual([
'currentFilter', 'currentTabCode',
'isCreatorOpen', 'isLoading', 'isOpenSavesSearches',
'isReady', 'isRoomInfoOpen', 'isRoomLinkOpen', 'isVisible',
'needsInit', 'needsSearch'
].sort());
});
it('useNavigatorActions returns sendSearch + reloadCurrentSearch', () =>
{
const { result } = renderHook(() => useNavigatorActions());
expect(typeof result.current.sendSearch).toBe('function');
expect(typeof result.current.reloadCurrentSearch).toBe('function');
});
});
+7 -68
View File
@@ -6,13 +6,13 @@ import { CanCreateRoomEventEvent, CantConnectMessageParser, CreateLinkEvent,
HabboWebTools, LegacyExternalInterface, NavigatorCategoryDataParser,
NavigatorEventCategoryDataParser, NavigatorHomeRoomEvent,
NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSavedSearch,
NavigatorSearchComposer, NavigatorSearchesEvent, NavigatorSearchEvent,
NavigatorSearchResultSet, NavigatorTopLevelContext, NitroEventType,
NavigatorSearchesEvent,
NavigatorTopLevelContext, NitroEventType,
RoomDataParser, RoomEnterErrorEvent, RoomEntryInfoMessageEvent,
RoomForwardEvent, RoomScoreEvent, RoomSettingsUpdatedEvent,
RoomForwardEvent, RoomScoreEvent,
SecurityLevel, UserEventCatsEvent, UserFlatCatsEvent,
UserInfoEvent, UserPermissionsEvent } from '@nitrots/nitro-renderer';
import { useCallback, useRef, useState } from 'react';
import { useCallback, useState } from 'react';
import { CreateRoomSession, GetConfigurationValue, INavigatorData,
LocalizeText, NotificationAlertType, SendMessageComposer,
TryVisitRoom, VisitDesktop } from '../../api';
@@ -27,7 +27,6 @@ export const useNavigatorStore = () =>
const [ favouriteRoomIds, setFavouriteRoomIds ] = useState<number[]>([]);
const [ topLevelContext, setTopLevelContext ] = useState<NavigatorTopLevelContext>(null);
const [ topLevelContexts, setTopLevelContexts ] = useState<NavigatorTopLevelContext[]>(null);
const [ searchResult, setSearchResult ] = useState<NavigatorSearchResultSet>(null);
const [ navigatorSearches, setNavigatorSearches ] = useState<NavigatorSavedSearch[]>(null);
const [ navigatorData, setNavigatorData ] = useState<INavigatorData>({
settingsReceived: false,
@@ -44,41 +43,8 @@ export const useNavigatorStore = () =>
canRate: true
});
// Refs let handlers stay [] deps without losing access to fresh state.
const topLevelContextsRef = useRef(topLevelContexts);
topLevelContextsRef.current = topLevelContexts;
const topLevelContextRef = useRef(topLevelContext);
topLevelContextRef.current = topLevelContext;
const searchResultRef = useRef(searchResult);
searchResultRef.current = searchResult;
const { simpleAlert = null } = useNotification();
const sendSearch = useCallback((searchValue: string, contextCode: string) =>
{
useNavigatorUiStore.getState().closeCreator();
SendMessageComposer(new NavigatorSearchComposer(contextCode, searchValue));
useNavigatorUiStore.getState().setLoading(true);
}, []);
const reloadCurrentSearch = useCallback(() =>
{
if(!useNavigatorUiStore.getState().isReady)
{
useNavigatorUiStore.getState().requestSearch();
return;
}
const sr = searchResultRef.current;
if(sr)
{
sendSearch(sr.data, sr.code);
return;
}
const ctx = topLevelContextRef.current;
if(!ctx) return;
sendSearch('', ctx.code);
}, [ sendSearch ]);
useMessageEvent<FavouritesEvent>(FavouritesEvent, useCallback(event =>
{
const parser = event.getParser();
@@ -99,12 +65,6 @@ export const useNavigatorStore = () =>
});
}, []));
useMessageEvent<RoomSettingsUpdatedEvent>(RoomSettingsUpdatedEvent, useCallback(event =>
{
const parser = event.getParser();
SendMessageComposer(new GetGuestRoomMessageComposer(parser.roomId, false, false));
}, []));
useMessageEvent<CanCreateRoomEventEvent>(CanCreateRoomEventEvent, useCallback(event =>
{
const parser = event.getParser();
@@ -223,28 +183,8 @@ export const useNavigatorStore = () =>
const parser = event.getParser();
setTopLevelContexts(parser.topLevelContexts);
setTopLevelContext(parser.topLevelContexts.length ? parser.topLevelContexts[0] : null);
}, []));
useMessageEvent<NavigatorSearchEvent>(NavigatorSearchEvent, useCallback(event =>
{
const parser = event.getParser();
const contexts = topLevelContextsRef.current;
setTopLevelContext(prev =>
{
let next = prev;
if(!next) next = (contexts && contexts.length && contexts[0]) || null;
if(!next) return null;
if(contexts && contexts.length)
{
for(const ctx of contexts)
{
if(ctx.code === parser.result.code) next = ctx;
}
}
return next;
});
setSearchResult(parser.result);
useNavigatorUiStore.getState().setLoading(false);
// Seed the query's tab code so useNavigatorSearch activates immediately
useNavigatorUiStore.getState().setTab(parser.topLevelContexts[0]?.code ?? '');
}, []));
useMessageEvent<UserFlatCatsEvent>(UserFlatCatsEvent, useCallback(event =>
@@ -342,7 +282,6 @@ export const useNavigatorStore = () =>
return {
categories, eventCategories, favouriteRoomIds,
topLevelContext, topLevelContexts,
searchResult, navigatorSearches, navigatorData,
sendSearch, reloadCurrentSearch
navigatorSearches, navigatorData
};
};
+4 -1
View File
@@ -11,8 +11,11 @@ export const useNavigatorUiState = () =>
const isLoading = useNavigatorUiStore(s => s.isLoading);
const needsInit = useNavigatorUiStore(s => s.needsInit);
const needsSearch = useNavigatorUiStore(s => s.needsSearch);
const currentTabCode = useNavigatorUiStore(s => s.currentTabCode);
const currentFilter = useNavigatorUiStore(s => s.currentFilter);
return {
isVisible, isReady, isCreatorOpen, isRoomInfoOpen, isRoomLinkOpen,
isOpenSavesSearches, isLoading, needsInit, needsSearch
isOpenSavesSearches, isLoading, needsInit, needsSearch,
currentTabCode, currentFilter
};
};