Template: Add a new query hook
Status: Reference
Task
Add a new query hook for <read operation> (e.g. "fetch club members", "fetch session matches").
Pre-implementation checks
- Grep for similar query:
grep -rn "useFind<X>\|useGet<X>\|use<X>List" packages/app/src/presentation/hooks/queries/. If a similar query exists, justify creating a new one OR extend the existing. - Confirm port + adapter exist: repository method must already exist in
packages/app/src/ports/and be implemented in adapters. - Pick freshness policy (will become explicit
@freshnessin Phase 14f):realtime— sessions, RSVPs, matches; pushed via Realtime channelsfrequent— leaderboards, open pickups, club elostandard— club list, session list, member list (default)stable— profile, preferences, venues
- Identify cache normalization opportunity: does the response shape match an entity that's stored in another query's cache? If yes, plan to use
writeX/writeXBackfillfrom@/presentation/cache.
Files in scope
- New:
packages/app/src/presentation/hooks/queries/use-<noun>.ts - Possibly:
packages/app/src/index.ts(barrel) - Possibly:
packages/app/src/query-keys.ts(add the key factory if missing)
Files OUT of scope
- Adapter changes — separate prior commit
- UI changes — separate later commit
Implementation pattern
ts
import { useEffect } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { <repo> } from '@/registry';
import { <Keys>, STALE_TIME } from '@/query-keys';
import { write<Entity> } from '@/presentation/cache'; // optional — only if normalization applies
import type { <Entity> } from '@/domain';
import type { UseQueryResult } from '@tanstack/react-query';
/**
* @freshness <realtime|frequent|standard|stable>
* @refetchOnFocus <true|false>
* @realtime <channel-key|null>
* @offline <read-from-cache|n/a>
*
* Fetch description.
*/
export function use<Noun>(<args>): UseQueryResult<<Entity> | null> {
const queryClient = useQueryClient();
const result = useQuery({
queryKey: <Keys>.<key-factory>(<args>),
queryFn: () => <repo>.<method>(<args>),
enabled: <args>.length > 0,
staleTime: STALE_TIME.<bucket>,
});
// Cache normalization: keep parent lists in sync (optional, when applicable)
useEffect(() => {
if (result.data) write<Entity>(queryClient, result.data);
}, [result.data, queryClient]);
return result;
}Hard constraints to follow (from AGENTS.md)
- DATA-8: cross-query entity writes via
writeX/writeXBackfill— never manualsetQueryData - Reviewer checklist:
CHK-DATA-2(useMemo not useQuery for pure CPU),CHK-DATA-3(don't fire the same key from two levels) — seedocs/best-practices/review-checklist.md
Post-implementation verification
bash
git status
yarn workspace @twomore/app typecheck
yarn workspace @twomore/app jest --runInBand src/config/__tests__/architecture-archunit.test.tsWhat to report
- File created
- Freshness policy chosen + reason
- Whether cache normalization wired (and which entity)
- Typecheck + arch test results