mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +00:00
Add useExternalSnapshot + useNitroEventReducer + useMessageEventReducer hooks
The three companions promised in docs/ARCHITECTURE.md proposal #1 ('Companion to add later') are now in src/hooks/events/: - useExternalSnapshot wraps useSyncExternalStore for the renderer's EventDispatcher.subscribe() + getXxxSnapshot() pairing introduced in Nitro_Render_V3 2.1.0. - useNitroEventReducer and useMessageEventReducer mirror the existing *State hooks but collapse multiple event types into a single owned state slice. The message variant accepts either a single event type or an array; subscription is wired through a single useEffect to keep the rules-of-hooks happy.
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
export * from './useEventDispatcher';
|
export * from './useEventDispatcher';
|
||||||
|
export * from './useExternalSnapshot';
|
||||||
export * from './useMessageEvent';
|
export * from './useMessageEvent';
|
||||||
|
export * from './useMessageEventReducer';
|
||||||
export * from './useMessageEventState';
|
export * from './useMessageEventState';
|
||||||
export * from './useNitroEvent';
|
export * from './useNitroEvent';
|
||||||
|
export * from './useNitroEventReducer';
|
||||||
export * from './useNitroEventState';
|
export * from './useNitroEventState';
|
||||||
export * from './useUiEvent';
|
export * from './useUiEvent';
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { useSyncExternalStore } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* useSyncExternalStore wrapper for the Nitro renderer's subscribe + snapshot
|
||||||
|
* getter contract.
|
||||||
|
*
|
||||||
|
* Pair with EventDispatcher.subscribe() (Nitro_Render_V3 v2.1.0+) and a
|
||||||
|
* referentially-stable snapshot getter such as
|
||||||
|
* SessionDataManager.getUserDataSnapshot() or
|
||||||
|
* RoomSessionManager.getActiveRoomSessionSnapshot().
|
||||||
|
*
|
||||||
|
* const userData = useExternalSnapshot(
|
||||||
|
* cb => GetEventDispatcher().subscribe(NitroEventType.SESSION_DATA_UPDATED, cb),
|
||||||
|
* () => GetSessionDataManager().getUserDataSnapshot()
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* Snapshot reference invariance is guaranteed by the renderer: the same
|
||||||
|
* object is returned across reads until the corresponding *_UPDATED event
|
||||||
|
* dispatches.
|
||||||
|
*/
|
||||||
|
export const useExternalSnapshot = <T,>(
|
||||||
|
subscribe: (onChange: () => void) => () => void,
|
||||||
|
getSnapshot: () => T,
|
||||||
|
getServerSnapshot?: () => T
|
||||||
|
): T => useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot ?? getSnapshot);
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { GetCommunication, IMessageEvent, MessageEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducer companion of useMessageEventState for the server message
|
||||||
|
* channel. Accepts either a single event type or an array of event types
|
||||||
|
* that all collapse into the same state slice — typical for streams where
|
||||||
|
* different composers update the same shape (e.g. furniture list +
|
||||||
|
* add/update + remove all maintain one GroupItem[]).
|
||||||
|
*
|
||||||
|
* const groupItems = useMessageEventReducer<GroupItem[], A | B | C>(
|
||||||
|
* [ParserA, ParserB, ParserC],
|
||||||
|
* (state, event) => {
|
||||||
|
* if (event instanceof ParserA) return applyA(state, event);
|
||||||
|
* if (event instanceof ParserB) return applyB(state, event);
|
||||||
|
* return state;
|
||||||
|
* },
|
||||||
|
* []
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* Closure stability via reducerRef refreshed in commit phase.
|
||||||
|
*/
|
||||||
|
export const useMessageEventReducer = <S, T extends IMessageEvent>(
|
||||||
|
eventType: typeof MessageEvent | (typeof MessageEvent)[],
|
||||||
|
reducer: (state: S, event: T) => S,
|
||||||
|
initial: S | (() => S)
|
||||||
|
): S =>
|
||||||
|
{
|
||||||
|
const [ value, setValue ] = useState<S>(initial);
|
||||||
|
const reducerRef = useRef(reducer);
|
||||||
|
|
||||||
|
useLayoutEffect(() =>
|
||||||
|
{
|
||||||
|
reducerRef.current = reducer;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handler = useCallback((event: T) =>
|
||||||
|
{
|
||||||
|
setValue(prev => reducerRef.current(prev, event));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const types = useMemo(() => Array.isArray(eventType) ? eventType : [ eventType ], [ eventType ]);
|
||||||
|
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
const communication = GetCommunication();
|
||||||
|
|
||||||
|
const registered = types.map(t =>
|
||||||
|
{
|
||||||
|
//@ts-ignore
|
||||||
|
const event = new t(handler);
|
||||||
|
|
||||||
|
communication.registerMessageEvent(event);
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
{
|
||||||
|
for(const event of registered) communication.removeMessageEvent(event);
|
||||||
|
};
|
||||||
|
}, [ types, handler ]);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||||
|
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||||
|
import { useNitroEvent } from './useNitroEvent';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducer companion of useNitroEventState for the cases where multiple
|
||||||
|
* event types collapse into a single state slice. Replaces the pattern:
|
||||||
|
*
|
||||||
|
* const [state, setState] = useState(initial);
|
||||||
|
* useNitroEvent(EVENT_A, e => setState(prev => reduceA(prev, e)));
|
||||||
|
* useNitroEvent(EVENT_B, e => setState(prev => reduceB(prev, e)));
|
||||||
|
*
|
||||||
|
* with:
|
||||||
|
*
|
||||||
|
* const state = useNitroEventReducer<S, A | B>(
|
||||||
|
* [EVENT_A, EVENT_B],
|
||||||
|
* (state, event) => {
|
||||||
|
* if (event instanceof EventA) return reduceA(state, event);
|
||||||
|
* if (event instanceof EventB) return reduceB(state, event);
|
||||||
|
* return state;
|
||||||
|
* },
|
||||||
|
* initial
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* Closure stability: the reducer ref is refreshed in commit phase, so a
|
||||||
|
* new reducer identity per render does not force the listener to
|
||||||
|
* re-subscribe.
|
||||||
|
*/
|
||||||
|
export const useNitroEventReducer = <S, T extends NitroEvent>(
|
||||||
|
types: string | string[],
|
||||||
|
reducer: (state: S, event: T) => S,
|
||||||
|
initial: S | (() => S),
|
||||||
|
enabled: boolean = true
|
||||||
|
): S =>
|
||||||
|
{
|
||||||
|
const [ value, setValue ] = useState<S>(initial);
|
||||||
|
const reducerRef = useRef(reducer);
|
||||||
|
|
||||||
|
useLayoutEffect(() =>
|
||||||
|
{
|
||||||
|
reducerRef.current = reducer;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handler = useCallback((event: T) =>
|
||||||
|
{
|
||||||
|
setValue(prev => reducerRef.current(prev, event));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useNitroEvent<T>(types, handler, enabled);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user