diff --git a/src/components/catalog/views/targeted-offer/OfferView.tsx b/src/components/catalog/views/targeted-offer/OfferView.tsx index eb8cead..4fa945b 100644 --- a/src/components/catalog/views/targeted-offer/OfferView.tsx +++ b/src/components/catalog/views/targeted-offer/OfferView.tsx @@ -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(null); + const offer = useMessageEventState( + TargetedOfferEvent, + evt => evt.getParser()?.data ?? null, + null + ); const [ opened, setOpened ] = useState(false); - useMessageEvent(TargetedOfferEvent, evt => - { - let parser = evt.getParser(); - - if(!parser) return; - - setOffer(parser.data); - }); - useEffect(() => { SendMessageComposer(new GetTargetedOfferComposer()); diff --git a/src/hooks/events/index.ts b/src/hooks/events/index.ts index 6d9903b..6b5c11e 100644 --- a/src/hooks/events/index.ts +++ b/src/hooks/events/index.ts @@ -1,4 +1,6 @@ export * from './useEventDispatcher'; export * from './useMessageEvent'; +export * from './useMessageEventState'; export * from './useNitroEvent'; +export * from './useNitroEventState'; export * from './useUiEvent'; diff --git a/src/hooks/events/useMessageEventState.ts b/src/hooks/events/useMessageEventState.ts new file mode 100644 index 0000000..11fa864 --- /dev/null +++ b/src/hooks/events/useMessageEventState.ts @@ -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 = ( + eventType: typeof MessageEvent, + selector: (event: T) => S, + initial: S | (() => S) +): S => +{ + const [ value, setValue ] = useState(initial); + const selectorRef = useRef(selector); + + useLayoutEffect(() => + { + selectorRef.current = selector; + }); + + const handler = useCallback((event: T) => + { + setValue(selectorRef.current(event)); + }, []); + + useMessageEvent(eventType, handler); + + return value; +}; diff --git a/src/hooks/events/useNitroEventState.ts b/src/hooks/events/useNitroEventState.ts new file mode 100644 index 0000000..48fab86 --- /dev/null +++ b/src/hooks/events/useNitroEventState.ts @@ -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 = ( + type: string | string[], + selector: (event: T) => S, + initial: S | (() => S), + enabled: boolean = true +): S => +{ + const [ value, setValue ] = useState(initial); + const selectorRef = useRef(selector); + + useLayoutEffect(() => + { + selectorRef.current = selector; + }); + + const handler = useCallback((event: T) => + { + setValue(selectorRef.current(event)); + }, []); + + useNitroEvent(type, handler, enabled); + + return value; +};