mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
Add useNitroEventState / useMessageEventState hooks (proposal #1)
Introduce the building block for reducing the state-from-event
boilerplate that pervades the codebase:
// Before
const [foo, setFoo] = useState(initial);
useNitroEvent(SOME_EVENT, e => setFoo(e.payload));
// After
const foo = useNitroEventState(SOME_EVENT, e => e.payload, initial);
Implementation notes:
- src/hooks/events/useNitroEventState.ts wraps useNitroEvent so the
selector closure can use up-to-date surrounding values (captured in
a ref refreshed in commit via useLayoutEffect) without forcing a
re-subscription on every render. Listener is registered once and
always reads the latest selector.
- src/hooks/events/useMessageEventState.ts is the mirror for
useMessageEvent (server message channel — request/response composers
and push parsers).
- Both pass the new react-hooks v7 rules cleanly (in particular the
strict react-hooks/refs that forbids ref mutation during render).
- Re-exported from src/hooks/events/index.ts so callers reach them
via the existing `from '../../hooks'` import path.
Pilot adoption (1 site) to demonstrate the pattern:
- src/components/catalog/views/targeted-offer/OfferView.tsx:
the offer state was a clean derive-from-event case
(setOffer(parser.data) on TargetedOfferEvent, no other writes).
Replaced with a single useMessageEventState call using the optional
chain `evt.getParser()?.data ?? null` as selector. Removes the
useState pair and the explicit subscription block.
Honest scope note:
A broader sweep is intentionally NOT done. Most existing event
subscriptions in this codebase are multi-state updates, state
machines, conditional filters ("skip if not my id"), or have side
effects mixed in (notifications, redirects). Forcing those into
useNitroEventState would lose information and risk regressions in
behavior the lint won't catch. Adoption should happen organically
when contributors see a clean derive-from-event case, not as a
mechanical replace-all.
https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
This commit is contained in:
@@ -1,24 +1,19 @@
|
||||
import { GetTargetedOfferComposer, TargetedOfferData, TargetedOfferEvent } from '@nitrots/nitro-renderer';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SendMessageComposer } from '../../../../api';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { useMessageEventState } from '../../../../hooks';
|
||||
import { OfferBubbleView } from './OfferBubbleView';
|
||||
import { OfferWindowView } from './OfferWindowView';
|
||||
|
||||
export const OfferView = () =>
|
||||
{
|
||||
const [ offer, setOffer ] = useState<TargetedOfferData>(null);
|
||||
const offer = useMessageEventState<TargetedOfferEvent, TargetedOfferData>(
|
||||
TargetedOfferEvent,
|
||||
evt => evt.getParser()?.data ?? null,
|
||||
null
|
||||
);
|
||||
const [ opened, setOpened ] = useState<boolean>(false);
|
||||
|
||||
useMessageEvent<TargetedOfferEvent>(TargetedOfferEvent, evt =>
|
||||
{
|
||||
let parser = evt.getParser();
|
||||
|
||||
if(!parser) return;
|
||||
|
||||
setOffer(parser.data);
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new GetTargetedOfferComposer());
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export * from './useEventDispatcher';
|
||||
export * from './useMessageEvent';
|
||||
export * from './useMessageEventState';
|
||||
export * from './useNitroEvent';
|
||||
export * from './useNitroEventState';
|
||||
export * from './useUiEvent';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { IMessageEvent, MessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useMessageEvent } from './useMessageEvent';
|
||||
|
||||
/**
|
||||
* Subscribe to a server message event and expose the latest derived
|
||||
* value as React state. Mirror of useNitroEventState for the Nitro
|
||||
* communication channel (request/response composers, push parsers).
|
||||
*
|
||||
* const data = useMessageEventState(SomeParser, e => e.getParser().data, null);
|
||||
*/
|
||||
export const useMessageEventState = <T extends IMessageEvent, S>(
|
||||
eventType: typeof MessageEvent,
|
||||
selector: (event: T) => S,
|
||||
initial: S | (() => S)
|
||||
): S =>
|
||||
{
|
||||
const [ value, setValue ] = useState<S>(initial);
|
||||
const selectorRef = useRef(selector);
|
||||
|
||||
useLayoutEffect(() =>
|
||||
{
|
||||
selectorRef.current = selector;
|
||||
});
|
||||
|
||||
const handler = useCallback((event: T) =>
|
||||
{
|
||||
setValue(selectorRef.current(event));
|
||||
}, []);
|
||||
|
||||
useMessageEvent<T>(eventType, handler);
|
||||
|
||||
return value;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NitroEvent } from '@nitrots/nitro-renderer';
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useNitroEvent } from './useNitroEvent';
|
||||
|
||||
/**
|
||||
* Subscribe to a Nitro renderer event and expose the latest derived value
|
||||
* as React state. Replaces the boilerplate pattern:
|
||||
*
|
||||
* const [foo, setFoo] = useState(initial);
|
||||
* useNitroEvent(EVENT, e => setFoo(selector(e)));
|
||||
*
|
||||
* with:
|
||||
*
|
||||
* const foo = useNitroEventState(EVENT, selector, initial);
|
||||
*
|
||||
* The selector closure is captured in a ref refreshed in commit, so
|
||||
* a new selector identity per render does not re-subscribe the listener.
|
||||
*/
|
||||
export const useNitroEventState = <T extends NitroEvent, S>(
|
||||
type: string | string[],
|
||||
selector: (event: T) => S,
|
||||
initial: S | (() => S),
|
||||
enabled: boolean = true
|
||||
): S =>
|
||||
{
|
||||
const [ value, setValue ] = useState<S>(initial);
|
||||
const selectorRef = useRef(selector);
|
||||
|
||||
useLayoutEffect(() =>
|
||||
{
|
||||
selectorRef.current = selector;
|
||||
});
|
||||
|
||||
const handler = useCallback((event: T) =>
|
||||
{
|
||||
setValue(selectorRef.current(event));
|
||||
}, []);
|
||||
|
||||
useNitroEvent<T>(type, handler, enabled);
|
||||
|
||||
return value;
|
||||
};
|
||||
Reference in New Issue
Block a user