Split useFriendRequestWidget into state + actions (flat hooks layout)

Stesso pattern di doorbell / poll / furni-chooser / user-chooser:
flat split sotto src/hooks/rooms/widgets/, no co-location dentro
src/components/.

Split
- src/hooks/rooms/widgets/useFriendRequestState.ts (new):
  activeRequests state + displayedRequests derived (filter su
  dismissedRequestIds) + due bridge events (user added/removed) +
  un useEffect che riallinea activeRequests quando cambia il set
  di requests dal friends-store. Esporta anche il tipo
  ActiveFriendRequest per consumi futuri.
  Plus: ?. su roomSession e userDataManager per evitare il bug
  pattern "session è null in transition" (vedi PetTrainingPanel,
  precedentemente fixato).
- src/hooks/rooms/widgets/useFriendRequestActions.ts (new):
  hideFriendRequest. Thin adapter sul friends-store
  (setDismissedRequestIds), nessuna subscription.
- src/hooks/rooms/widgets/useFriendRequestWidget.ts: deprecated
  shim che compone i due e preserva
  { displayedRequests, hideFriendRequest } per il consumer
  FriendRequestWidgetView.

Verifica
- yarn eslint sui 4 file toccati: 1 errore pre-esistente
  (set-state-in-effect sul useEffect che ri-derive activeRequests
  da requests — già nel file originale, baseline invariata).
- yarn test: 49/49 passing.
- yarn tsc: clean.

Sequence widget split adesso a 5 (doorbell, poll, furni-chooser,
user-chooser, friend-request). Rimangono: usePetPackageWidget,
useWordQuizWidget, useChatInputWidget, useChatWidget,
useAvatarInfoWidget, useFilterWordsWidget.
This commit is contained in:
simoleo89
2026-05-11 17:47:09 +00:00
parent 85fc82794d
commit f3442f8aa0
4 changed files with 125 additions and 76 deletions
+2
View File
@@ -7,6 +7,8 @@ export * from './useDoorbellActions';
export * from './useDoorbellState';
export * from './useDoorbellWidget';
export * from './useFilterWordsWidget';
export * from './useFriendRequestActions';
export * from './useFriendRequestState';
export * from './useFriendRequestWidget';
export * from './useFurniChooserActions';
export * from './useFurniChooserState';
@@ -0,0 +1,28 @@
import { useFriends } from '../../friends';
/**
* Imperative actions for the friend-request widget. Stateless — split
* from useFriendRequestState so a component that only needs to dismiss
* a request doesn't subscribe to the user-added/removed lifecycle.
*
* The actual dismissal flag lives in the shared friends store
* (via `setDismissedRequestIds`), so this hook is a thin adapter.
*/
export const useFriendRequestActions = () =>
{
const { setDismissedRequestIds = null } = useFriends();
return {
hideFriendRequest: (userId: number): void =>
{
if(!setDismissedRequestIds) return;
setDismissedRequestIds(prevValue =>
{
if(prevValue.indexOf(userId) >= 0) return prevValue;
return [ ...prevValue, userId ];
});
}
};
};
@@ -0,0 +1,85 @@
import { RoomObjectCategory, RoomObjectUserType } from '@nitrots/nitro-renderer';
import { useEffect, useMemo, useState } from 'react';
import { GetRoomSession, MessengerRequest } from '../../../api';
import { useFriends } from '../../friends';
import { useUserAddedEvent, useUserRemovedEvent } from '../engine';
export interface ActiveFriendRequest
{
roomIndex: number;
request: MessengerRequest;
}
/**
* State + event subscriptions for the friend-request room widget.
* Pure imperative action (hideFriendRequest) lives in
* useFriendRequestActions.
*/
export const useFriendRequestState = () =>
{
const [ activeRequests, setActiveRequests ] = useState<ActiveFriendRequest[]>([]);
const { requests = [], dismissedRequestIds = [] } = useFriends();
const displayedRequests = useMemo(
() => activeRequests.filter(entry => (dismissedRequestIds.indexOf(entry.request.requesterUserId) === -1)),
[ activeRequests, dismissedRequestIds ]
);
useUserAddedEvent(true, event =>
{
if(event.category !== RoomObjectCategory.UNIT) return;
const userData = GetRoomSession()?.userDataManager?.getUserDataByIndex(event.id);
if(!userData || (userData.type !== RoomObjectUserType.getTypeNumber(RoomObjectUserType.USER))) return;
const request = requests.find(r => (r.requesterUserId === userData.webID));
if(!request) return;
setActiveRequests(prev =>
{
if(prev.find(entry => (entry.request.requesterUserId === userData.webID))) return prev;
return [ ...prev, { roomIndex: userData.roomIndex, request } ];
});
});
useUserRemovedEvent(true, event =>
{
if(event.category !== RoomObjectCategory.UNIT) return;
setActiveRequests(prev =>
{
const index = prev.findIndex(entry => (entry.roomIndex === event.id));
if(index === -1) return prev;
const next = [ ...prev ];
next.splice(index, 1);
return next;
});
});
useEffect(() =>
{
const session = GetRoomSession();
if(!session) return;
const next: ActiveFriendRequest[] = [];
for(const request of requests)
{
const userData = session.userDataManager?.getUserData(request.requesterUserId);
if(!userData) continue;
next.push({ roomIndex: userData.roomIndex, request });
}
setActiveRequests(next);
}, [ requests ]);
return { displayedRequests };
};
@@ -1,81 +1,15 @@
import { RoomObjectCategory, RoomObjectUserType } from '@nitrots/nitro-renderer';
import { useEffect, useMemo, useState } from 'react';
import { GetRoomSession, MessengerRequest } from '../../../api';
import { useFriends } from '../../friends';
import { useUserAddedEvent, useUserRemovedEvent } from '../engine';
import { useFriendRequestActions } from './useFriendRequestActions';
import { useFriendRequestState } from './useFriendRequestState';
const useFriendRequestWidgetState = () =>
/**
* @deprecated Use `useFriendRequestState` and `useFriendRequestActions`
* directly. This shim preserves the
* `{ displayedRequests, hideFriendRequest }` shape for existing consumers.
*/
export const useFriendRequestWidget = () =>
{
const [ activeRequests, setActiveRequests ] = useState<{ roomIndex: number, request: MessengerRequest }[]>([]);
const { requests = [], dismissedRequestIds = [], setDismissedRequestIds = null } = useFriends();
const displayedRequests = useMemo(() => activeRequests.filter(request => (dismissedRequestIds.indexOf(request.request.requesterUserId) === -1)), [ activeRequests, dismissedRequestIds ]);
const hideFriendRequest = (userId: number) =>
{
setDismissedRequestIds(prevValue =>
{
if(prevValue.indexOf(userId) >= 0) return prevValue;
const newValue = [ ...prevValue ];
newValue.push(userId);
return newValue;
});
};
useUserAddedEvent(true, event =>
{
if(event.category !== RoomObjectCategory.UNIT) return;
const userData = GetRoomSession().userDataManager.getUserDataByIndex(event.id);
if(!userData || (userData.type !== RoomObjectUserType.getTypeNumber(RoomObjectUserType.USER))) return;
const request = requests.find(request => (request.requesterUserId === userData.webID));
if(!request || activeRequests.find(request => (request.request.requesterUserId === userData.webID))) return;
const newValue = [ ...activeRequests ];
newValue.push({ roomIndex: userData.roomIndex, request });
setActiveRequests(newValue);
});
useUserRemovedEvent(true, event =>
{
if(event.category !== RoomObjectCategory.UNIT) return;
const index = activeRequests.findIndex(request => (request.roomIndex === event.id));
if(index === -1) return;
const newValue = [ ...activeRequests ];
newValue.splice(index, 1);
setActiveRequests(newValue);
});
useEffect(() =>
{
const newDisplayedRequests: { roomIndex: number, request: MessengerRequest }[] = [];
for(const request of requests)
{
const userData = GetRoomSession().userDataManager.getUserData(request.requesterUserId);
if(!userData) continue;
newDisplayedRequests.push({ roomIndex: userData.roomIndex, request });
}
setActiveRequests(newDisplayedRequests);
}, [ requests ]);
const { displayedRequests } = useFriendRequestState();
const { hideFriendRequest } = useFriendRequestActions();
return { displayedRequests, hideFriendRequest };
};
export const useFriendRequestWidget = useFriendRequestWidgetState;