mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +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 './useExternalSnapshot';
|
||||
export * from './useMessageEvent';
|
||||
export * from './useMessageEventReducer';
|
||||
export * from './useMessageEventState';
|
||||
export * from './useNitroEvent';
|
||||
export * from './useNitroEventReducer';
|
||||
export * from './useNitroEventState';
|
||||
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