refactor(navigator): migrate all 13 consumers off useNavigator god-hook

Mechanical swap to the new filter hooks landed in the previous commits:
- NavigatorDoorStateView -> useDoorState (snapshot/setSnapshot/reset)
- NavigatorView -> useNavigatorData + useNavigatorUiState +
  useNavigatorActions + direct useNavigatorUiStore.getState() in handlers
  (linkTracker collapsed to a dispatch table; 9 useState gone)
- NavigatorSearchView -> useNavigatorData + useNavigatorActions
  (sendSearch prop drilling removed)
- NavigatorSearchResultItemView -> useDoorState (setSnapshot aliased as
  setDoorData; call sites unchanged - DoorStateSnapshot is compatible)
- 9 bulk consumers (one-line import swap) -> useNavigatorData

Zero behavioural change intended. yarn typecheck + yarn test --run +
yarn lint:hooks all clean on this commit.
This commit is contained in:
simoleo89
2026-05-27 18:58:03 +02:00
parent 3c10ccdaee
commit 1d580e6d24
13 changed files with 109 additions and 225 deletions
+63 -157
View File
@@ -1,6 +1,6 @@
import { NitroCard } from '@layout/NitroCard';
import { AddLinkEventTracker, ConvertGlobalRoomIdMessageComposer, FindNewFriendsMessageComposer, HabboWebTools, ILinkEventTracker, LegacyExternalInterface, NavigatorInitComposer, NavigatorSearchComposer, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useRef, useState } from 'react';
import { AddLinkEventTracker, ConvertGlobalRoomIdMessageComposer, FindNewFriendsMessageComposer, HabboWebTools, ILinkEventTracker, LegacyExternalInterface, NavigatorInitComposer, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer';
import { FC, useEffect, useRef } from 'react';
import { FaPlus } from 'react-icons/fa';
import savesSearchIcon from '../../assets/images/navigator/saves-search/search_save.png';
import createRoomImg from '../../assets/images/navigator/create_room.png';
@@ -8,7 +8,7 @@ import randomRoomImg from '../../assets/images/navigator/random_room.png';
import promoteRoomImg from '../../assets/images/navigator/promote_room.png';
import { CreateLinkEvent, LocalizeText, SendMessageComposer, TryVisitRoom } from '../../api';
import { Flex, Text } from '../../common';
import { useNavigator, useNitroEvent } from '../../hooks';
import { useNavigatorActions, useNavigatorData, useNavigatorUiState, useNavigatorUiStore, useNitroEvent } from '../../hooks';
import { NavigatorDoorStateView } from './views/NavigatorDoorStateView';
import { NavigatorRoomCreatorView } from './views/NavigatorRoomCreatorView';
import { NavigatorRoomInfoView } from './views/NavigatorRoomInfoView';
@@ -20,184 +20,106 @@ import { NavigatorSearchView } from './views/search/NavigatorSearchView';
export const NavigatorView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState(false);
const [ isReady, setIsReady ] = useState(false);
const [ isCreatorOpen, setCreatorOpen ] = useState(false);
const [ isRoomInfoOpen, setRoomInfoOpen ] = useState(false);
const [ isRoomLinkOpen, setRoomLinkOpen ] = useState(false);
const [ isOpenSavesSearches, setIsOpenSavesSearches ] = useState(false);
const [ isLoading, setIsLoading ] = useState(false);
const [ needsInit, setNeedsInit ] = useState(true);
const [ needsSearch, setNeedsSearch ] = useState(false);
const { searchResult = null, topLevelContext = null, topLevelContexts = null, navigatorData = null, navigatorSearches = null } = useNavigator();
const { searchResult, topLevelContext, topLevelContexts, navigatorData, navigatorSearches } = useNavigatorData();
const { isVisible, isReady, isCreatorOpen, isRoomInfoOpen, isRoomLinkOpen, isOpenSavesSearches, isLoading, needsInit, needsSearch } = useNavigatorUiState();
const { sendSearch, reloadCurrentSearch } = useNavigatorActions();
const pendingSearch = useRef<{ value: string, code: string }>(null);
const elementRef = useRef<HTMLDivElement>(null);
useNitroEvent<RoomSessionEvent>(RoomSessionEvent.CREATED, event =>
{
setIsVisible(false);
setCreatorOpen(false);
useNavigatorUiStore.getState().hide();
useNavigatorUiStore.getState().closeCreator();
});
const sendSearch = useCallback((searchValue: string, contextCode: string) =>
{
setCreatorOpen(false);
SendMessageComposer(new NavigatorSearchComposer(contextCode, searchValue));
setIsLoading(true);
}, []);
const reloadCurrentSearch = useCallback(() =>
{
if(!isReady)
{
setNeedsSearch(true);
return;
}
if(pendingSearch.current)
{
sendSearch(pendingSearch.current.value, pendingSearch.current.code);
pendingSearch.current = null;
return;
}
if(searchResult)
{
sendSearch(searchResult.data, searchResult.code);
return;
}
if(!topLevelContext) return;
sendSearch('', topLevelContext.code);
}, [ isReady, searchResult, topLevelContext, sendSearch ]);
useEffect(() =>
{
const linkTracker: ILinkEventTracker = {
linkReceived: (url: string) =>
{
const parts = url.split('/');
if(parts.length < 2) return;
const store = useNavigatorUiStore.getState();
switch(parts[1])
{
case 'show': {
setIsVisible(true);
setNeedsSearch(true);
case 'show':
store.show();
return;
}
case 'hide':
setIsVisible(false);
store.hide();
return;
case 'toggle': {
if(isVisible)
{
setIsVisible(false);
return;
}
setIsVisible(true);
setNeedsSearch(true);
case 'toggle':
store.toggle();
return;
}
case 'toggle-room-info':
setRoomInfoOpen(value => !value);
store.toggleRoomInfo();
return;
case 'toggle-room-link':
setRoomLinkOpen(value => !value);
store.toggleRoomLink();
return;
case 'goto':
if(parts.length <= 2) return;
switch(parts[2])
if(parts[2] === 'home')
{
case 'home':
if(navigatorData.homeRoomId <= 0) return;
TryVisitRoom(navigatorData.homeRoomId);
break;
default: {
const roomId = parseInt(parts[2]);
TryVisitRoom(roomId);
}
if(navigatorData.homeRoomId <= 0) return;
TryVisitRoom(navigatorData.homeRoomId);
return;
}
TryVisitRoom(parseInt(parts[2]));
return;
case 'create':
setIsVisible(true);
setCreatorOpen(true);
store.openCreator();
return;
case 'search':
if(parts.length > 2)
{
const topLevelContextCode = parts[2];
let searchValue = '';
if(parts.length > 3) searchValue = parts[3];
pendingSearch.current = { value: searchValue, code: topLevelContextCode };
setIsVisible(true);
setNeedsSearch(true);
}
if(parts.length <= 2) return;
pendingSearch.current = { value: parts.length > 3 ? parts[3] : '', code: parts[2] };
store.show();
return;
}
},
eventUrlPrefix: 'navigator/'
};
AddLinkEventTracker(linkTracker);
return () => RemoveLinkEventTracker(linkTracker);
}, [ isVisible, navigatorData ]);
}, [ navigatorData ]);
useEffect(() =>
{
if(!searchResult) return;
setIsLoading(false);
if(elementRef && elementRef.current) elementRef.current.scrollTop = 0;
if(elementRef.current) elementRef.current.scrollTop = 0;
}, [ searchResult ]);
useEffect(() =>
{
if(!isVisible || !isReady || !needsSearch) return;
reloadCurrentSearch();
setNeedsSearch(false);
}, [ isVisible, isReady, needsSearch, reloadCurrentSearch ]);
if(pendingSearch.current)
{
sendSearch(pendingSearch.current.value, pendingSearch.current.code);
pendingSearch.current = null;
}
else
{
reloadCurrentSearch();
}
useNavigatorUiStore.getState().consumeSearchRequest();
}, [ isVisible, isReady, needsSearch, sendSearch, reloadCurrentSearch ]);
useEffect(() =>
{
if(isReady || !topLevelContext) return;
setIsReady(true);
useNavigatorUiStore.getState().markReady();
}, [ isReady, topLevelContext ]);
useEffect(() =>
{
if(!isVisible || !needsInit) return;
SendMessageComposer(new NavigatorInitComposer());
setNeedsInit(false);
useNavigatorUiStore.getState().markInitDone();
}, [ isVisible, needsInit ]);
useEffect(() =>
{
LegacyExternalInterface.addCallback(HabboWebTools.OPENROOM, (k: string, _arg_2: boolean = false, _arg_3: string = null) => SendMessageComposer(new ConvertGlobalRoomIdMessageComposer(k)));
LegacyExternalInterface.addCallback(HabboWebTools.OPENROOM, (k: string) => SendMessageComposer(new ConvertGlobalRoomIdMessageComposer(k)));
}, []);
return (
@@ -208,28 +130,24 @@ export const NavigatorView: FC<{}> = props =>
uniqueKey="navigator">
<NitroCard.Header
headerText={ LocalizeText(isCreatorOpen ? 'navigator.createroom.title' : 'navigator.title') }
onCloseClick={ event => setIsVisible(false) } />
onCloseClick={ () => useNavigatorUiStore.getState().hide() } />
<NitroCard.Tabs>
<NitroCard.TabItem
isActive={ isOpenSavesSearches }
title={ LocalizeText('navigator.tooltip.left.show.hide') }
onClick={ () => setIsOpenSavesSearches(prev => !prev) }>
onClick={ () => useNavigatorUiStore.getState().toggleSavesSearches() }>
<img src={ savesSearchIcon } alt="" style={{ width: 18, height: 18 }} />
</NitroCard.TabItem>
{ topLevelContexts && (topLevelContexts.length > 0) && topLevelContexts.map((context, index) =>
{
return (
<NitroCard.TabItem
key={ index }
isActive={ ((topLevelContext === context) && !isCreatorOpen) }
onClick={ event => sendSearch('', context.code) }>
{ LocalizeText(('navigator.toplevelview.' + context.code)) }
</NitroCard.TabItem>
);
}) }
{ topLevelContexts && topLevelContexts.length > 0 && topLevelContexts.map((context, index) =>
<NitroCard.TabItem
key={ index }
isActive={ topLevelContext === context && !isCreatorOpen }
onClick={ () => sendSearch('', context.code) }>
{ LocalizeText('navigator.toplevelview.' + context.code) }
</NitroCard.TabItem>) }
<NitroCard.TabItem
isActive={ isCreatorOpen }
onClick={ event => setCreatorOpen(true) }>
onClick={ () => useNavigatorUiStore.getState().openCreator() }>
<FaPlus className="fa-icon" />
</NitroCard.TabItem>
</NitroCard.Tabs>
@@ -241,49 +159,37 @@ export const NavigatorView: FC<{}> = props =>
<NavigatorSearchSavesResultView searches={ navigatorSearches || [] } />
</div> }
<div className="flex flex-col w-full overflow-hidden gap-2">
<NavigatorSearchView sendSearch={ sendSearch } />
<NavigatorSearchView />
<div ref={ elementRef } className="flex flex-col flex-1 min-h-0 overflow-auto gap-2">
{ (searchResult && searchResult.results.map((result, index) => <NavigatorSearchResultView key={ index } searchResult={ result } />)) }
{ (searchResult && (!searchResult.results || (searchResult.results.length === 0))) &&
{ searchResult && searchResult.results.map((result, index) => <NavigatorSearchResultView key={ index } searchResult={ result } />) }
{ searchResult && (!searchResult.results || searchResult.results.length === 0) &&
<div className="nitro-card-panel px-3 py-2 text-sm text-muted">
{ LocalizeText(searchResult.code === 'myworld_view' ? 'navigator.roomsettings.moderation.none' : 'navigator.search.returned.no.results') }
</div> }
</div>
<Flex className="nitro-card-divider pt-2 border-t gap-2">
<Flex
pointer
alignItems="center"
justifyContent="center"
<Flex pointer alignItems="center" justifyContent="center"
className="flex-1 h-[60px] cursor-pointer bg-no-repeat pl-16"
style={ { backgroundImage: `url(${ createRoomImg })`, backgroundSize: '100% 100%' } }
onClick={ () => setCreatorOpen(true) }
>
onClick={ () => useNavigatorUiStore.getState().openCreator() }>
<Text variant="white" bold className="text-xs drop-shadow">
{ LocalizeText('navigator.createroom.create') }
</Text>
</Flex>
{ (searchResult?.code !== 'myworld_view' && searchResult?.code !== 'roomads_view') &&
<Flex
pointer
alignItems="center"
justifyContent="center"
{ searchResult?.code !== 'myworld_view' && searchResult?.code !== 'roomads_view' &&
<Flex pointer alignItems="center" justifyContent="center"
className="flex-1 h-[60px] cursor-pointer bg-no-repeat pl-16"
style={ { backgroundImage: `url(${ randomRoomImg })`, backgroundSize: '100% 100%' } }
onClick={ () => SendMessageComposer(new FindNewFriendsMessageComposer()) }
>
onClick={ () => SendMessageComposer(new FindNewFriendsMessageComposer()) }>
<Text variant="white" bold className="text-xs drop-shadow">
{ LocalizeText('navigator.random.room') }
</Text>
</Flex> }
{ (searchResult?.code === 'myworld_view' || searchResult?.code === 'roomads_view') &&
<Flex
pointer
alignItems="center"
justifyContent="center"
<Flex pointer alignItems="center" justifyContent="center"
className="flex-1 h-[60px] cursor-pointer bg-no-repeat pl-16"
style={ { backgroundImage: `url(${ promoteRoomImg })`, backgroundSize: '100% 100%' } }
onClick={ () => CreateLinkEvent('catalog/open/room_event') }
>
onClick={ () => CreateLinkEvent('catalog/open/room_event') }>
<Text variant="white" bold className="text-xs drop-shadow">
{ LocalizeText('navigator.promote.room') }
</Text>
@@ -295,8 +201,8 @@ export const NavigatorView: FC<{}> = props =>
</NitroCard.Content>
</NitroCard> }
<NavigatorDoorStateView />
{ isRoomInfoOpen && <NavigatorRoomInfoView onCloseClick={ () => setRoomInfoOpen(false) } /> }
{ isRoomLinkOpen && <NavigatorRoomLinkView onCloseClick={ () => setRoomLinkOpen(false) } /> }
{ isRoomInfoOpen && <NavigatorRoomInfoView onCloseClick={ () => useNavigatorUiStore.getState().setRoomInfoOpen(false) } /> }
{ isRoomLinkOpen && <NavigatorRoomLinkView onCloseClick={ () => useNavigatorUiStore.getState().setRoomLinkOpen(false) } /> }
<NavigatorRoomSettingsView />
</>
);