| name | add-query |
| description | Use when reading server data in the mobile app - wiring an @ecency/sdk query option into a screen, adding or editing a hook under src/providers/queries/, paginating a feed or list with an infinite query, or fixing a query key, enabled guard, or cache persistence problem. |
| argument-hint | ["query-name"] |
Add Query
Read CLAUDE.md first (State Management, SDK Migration). Writes are a separate skill: add-mutation.
Rule: the SDK owns the fetch
@ecency/sdk exports a large family of get*QueryOptions helpers. Most files under
src/providers/queries/ import from @ecency/sdk; a bare queryFn: is rare in that directory.
Search the SDK for your own domain first. Write a queryFn only when that search comes back empty:
D=node_modules/@ecency/sdk/dist/browser/index.d.ts
test -f "$D" || echo 'SDK not installed, run yarn'
grep -o "get[A-Za-z]*QueryOptions" "$D" | sort -u | grep -i draft
grep -n "declare function getPostQueryOptions" "$D"
Drop the trailing | grep -i draft to list them all. The last grep gives the real argument order.
Do not guess it. In the call sites here, getPostQueryOptions takes the observer third.
1. Straight from a component
import { useQuery } from '@tanstack/react-query';
import { getPostQueryOptions, getAccountFullQueryOptions } from '@ecency/sdk';
const observer = currentAccount?.name;
const { data: post, isLoading } = useQuery(getPostQueryOptions(author, permlink, observer));
const { data: account } = useQuery(getAccountFullQueryOptions(author));
2. App hook that adds mobile-only options
The dominant shape: spread the SDK options, then override. Verbatim,
src/providers/queries/leaderboardQueries/leaderboardQueries.ts:
import { useQuery } from '@tanstack/react-query';
import { getDiscoverLeaderboardQueryOptions } from '@ecency/sdk';
export const useGetLeaderboardQuery = (duration: 'day' | 'week' | 'month') => {
return useQuery({
...getDiscoverLeaderboardQueryOptions(duration),
refetchOnWindowFocus: true,
});
};
Usual overrides: enabled, select, staleTime, gcTime, initialData. Keep the SDK's
queryKey plus queryFn so the cache entry stays shared with other surfaces.
3. Private-API queries need the auth pair
Ecency backend queries take username plus an access token. Use useAuth() rather than
re-deriving it. From src/providers/queries/newsletterQueries.ts:
import { useAuth } from '../../hooks';
export const useDigestSubscriptionsQuery = () => {
const { username, code } = useAuth();
return useQuery(getDigestSubscriptionsQueryOptions(username, code));
};
4. Infinite queries
SDK get*InfiniteQueryOptions already carry initialPageParam plus getNextPageParam. The repo
rarely hand-rolls those two. Flatten in the hook, do not re-key. From
src/providers/queries/draftQueries.ts (comments stripped):
const { username, code } = useAuth();
const enabled = !!username && !!code;
const infiniteQuery = useInfiniteQuery({
...getDraftsInfiniteQueryOptions(username ?? '', code ?? '', limit),
enabled,
});
const data = useMemo(() => {
if (!infiniteQuery.data?.pages) return [];
return infiniteQuery.data.pages.flatMap((page) => page.data);
}, [infiniteQuery.data?.pages]);
return { ...infiniteQuery, data, pagesLoaded: infiniteQuery.data?.pages?.length ?? 0 };
5. Query keys
- SDK keys:
import { QueryKeys } from '@ecency/sdk'. Namespaced factories, not strings:
QueryKeys.posts.draftsInfinite(username, limit), QueryKeys.accounts.full(name),
QueryKeys.polls.details(author, permlink). Use these to invalidate or seed an SDK cache entry.
- Mobile-only keys:
src/providers/queries/queryKeys.ts is a default export named QUERIES
with a nested shape. Files import that default (import QUERIES from '<relative>/queryKeys',
so the specifier depends on the file) then queryKey: [QUERIES.WALLET.GET_ACTIVITIES, username].
There is no local QueryKeys export.
6. Hand-rolled query (last resort)
Only when the SDK has nothing. Verbatim, src/providers/queries/settingsQueries.ts:
export const useGetServersQuery = () => {
return useQuery<string[]>({
queryKey: [QUERIES.SETTINGS.GET_SERVERS],
queryFn: getNodes,
placeholderData: [...SERVER_LIST],
staleTime: 0,
});
};
getNodes comes from src/providers/ecency/ecency.ts, a provider module, not a bare fetch.
7. Export
src/providers/queries/index.ts uses export * from './<domain>Queries'. It also re-exports
getQueryClient from the SDK; the rest of the file is local (initQueryClient plus the
persistence allowlist). A subdirectory carries its own index.ts that re-exports namespaces.
Gotchas
- Persistence is an allowlist.
_shouldDehydrateQuery in src/providers/queries/index.ts
switches on queryKey[0], then narrows on queryKey[1]. A namespace with no case falls to the
default and is dropped, so a new namespace is not persisted until you add its case. Within a
case the subtype handling differs per namespace: some subtypes are dropped, some persist only
while a single page is loaded, some persist wholesale. Read the switch before assuming a new key
persists.
- Guard with
enabled when a param can be undefined. An undefined anywhere in a query key
also blocks persistence.
- Returning
undefined from getNextPageParam stops pagination; that is what the repo's
hand-rolled case returns.
- Optimistic vote data is no longer Redux. Call
updateVoteInQueryCaches() and read back via
applyRecentVoteOverrideToEntry() from src/providers/queries/postQueries/voteCacheUtils.ts;
seed a post before navigation with usePostsCachePrimer(). useInjectVotesCache is gone,
though CLAUDE.md's Post Data Flow still lists it.
- Non-React code: import
getQueryClient from the app barrel providers/queries rather than
the SDK: await queryClient.fetchQuery(getAccountsQueryOptions([username])).
src/providers/queries/sdk-config.ts runs from initQueryClient() and configures
ConfigManager (query client, private API host, image host, Hive nodes, DMCA lists). Adding a
query rarely requires touching it.
Prettier width is 100 (.prettierrc). Finish with yarn lint, yarn typecheck plus
yarn test:ci; .github/workflows/test.yml runs all three on every PR. The baseline in
tsc-baseline.json is empty, so any type error fails. Two co-located tests in
src/providers/queries/ read index.ts and assert the export * from './<domain>Queries'; line
from section 7, so a missing barrel export fails jest.