useNitroQuery: add accept() predicate; migrate two mod-tools chatlog views

Many composer/parser pairs on the Nitro wire are correlation-key based:
the request carries a key (roomId, issueId, etc.) and the response shows
up on the globally-shared event bus, where other components may be
listening for the same parser type with a different key. The previous
useNitroQuery resolved on the FIRST matching parser event regardless of
key — useless for that pattern, which is why two obvious migration
targets (ModToolsChatlogView, CfhChatlogView) were skipped earlier.

Adapter change
- New optional `accept?: (event) => boolean` on NitroQueryConfig.
- In awaitNitroResponse, events for which accept returns false are
  IGNORED rather than resolving the promise. The listener stays
  registered, the timeout still applies. This lets callers do:
    accept: e => e.getParser()?.data.roomId === roomId

Migrations
- src/components/mod-tools/views/room/ModToolsChatlogView.tsx
  - Was: useState<ChatRecordData>(null) + useMessageEvent with
    `if (parser.data.roomId !== roomId) return; setRoomChatlog(...)` +
    a mount-only useEffect dispatching the composer.
  - Now: a single useNitroQuery call keyed on roomId; accept filters
    by roomId; the query is enabled only when roomId is set.
    The composer is no longer re-dispatched on remount within
    staleTime; switching to a different room still triggers a fresh
    fetch because the queryKey changes.
- src/components/mod-tools/views/tickets/CfhChatlogView.tsx
  - Same pattern, keyed on issueId.

Both migrations drop ~15 lines per file (no more local state + manual
listener + manual send) while gaining cache/dedup/loading/error
handling from TanStack Query.

Verification
- yarn eslint on the four files: 1 pre-existing error (the
  IMessageEvent "redundant union" false positive in createNitroQuery
  that we already documented — local sandbox doesn't have the
  renderer SDK installed, so its types resolve as `any`).
- yarn test: 49/49 passing.
- yarn tsc on the four files: clean.
This commit is contained in:
simoleo89
2026-05-11 17:00:06 +00:00
parent bb09a562f6
commit bf84a0c2a6
3 changed files with 31 additions and 36 deletions
+13 -4
View File
@@ -23,6 +23,14 @@ export interface NitroQueryConfig<TParser extends IMessageEvent, TData>
* Maps the parser event to the data the component cares about.
*/
select?: (event: TParser) => TData;
/**
* Optional predicate to ignore parser events that don't match this
* query (typically used as a correlation-key filter on a globally
* shared event stream — e.g. `e => e.getParser()?.roomId === roomId`).
* When the predicate returns false, the listener stays registered
* and keeps waiting; the timeout still applies.
*/
accept?: (event: TParser) => boolean;
/**
* Max time to wait for the response before rejecting (default 15s).
*/
@@ -52,11 +60,11 @@ export const useNitroQuery = <TParser extends IMessageEvent, TData = TParser>(
config: NitroQueryConfig<TParser, TData>
): UseQueryResult<TData> =>
{
const { key, request, parser, select, timeoutMs = 15_000, enabled, staleTime, refetchOnMount } = config;
const { key, request, parser, select, accept, timeoutMs = 15_000, enabled, staleTime, refetchOnMount } = config;
const options: UseQueryOptions<TData, Error, TData> = {
queryKey: key,
queryFn: () => awaitNitroResponse<TParser, TData>({ key, request, parser, select, timeoutMs }),
queryFn: () => awaitNitroResponse<TParser, TData>({ key, request, parser, select, accept, timeoutMs }),
enabled,
staleTime,
refetchOnMount
@@ -71,11 +79,11 @@ export const useNitroQuery = <TParser extends IMessageEvent, TData = TParser>(
* can use the same plumbing imperatively.
*/
export const awaitNitroResponse = <TParser extends IMessageEvent, TData>(
config: Pick<NitroQueryConfig<TParser, TData>, 'request' | 'parser' | 'select' | 'timeoutMs'>
config: Pick<NitroQueryConfig<TParser, TData>, 'request' | 'parser' | 'select' | 'accept' | 'timeoutMs'>
): Promise<TData> =>
new Promise<TData>((resolve, reject) =>
{
const { request, parser: ParserCtor, select, timeoutMs = 15_000 } = config;
const { request, parser: ParserCtor, select, accept, timeoutMs = 15_000 } = config;
let settled = false;
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
@@ -90,6 +98,7 @@ export const awaitNitroResponse = <TParser extends IMessageEvent, TData>(
listener = new (ParserCtor as any)((event: TParser) =>
{
if(settled) return;
if(accept && !accept(event)) return;
settled = true;
cleanup();