networking-and-server-state
Builds TanStack Query server state over an ApiClient - validated responses, keys, retries, optimistic writes, offline.
Skill by zakariaf
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/zakariaf-networking-and-server-state
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: backend, data
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
- Runs as
- Node.js package
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/networking-and-server-state
What's inside
- Skill name
networking-and-server-state- Description
- Builds TanStack Query server state over an ApiClient - validated responses, keys, retries, optimistic writes, offline. Use when calling an API, adding a query or mutation, or data is stale. Not for local data (persistence-and-migrations).
- License
- MIT
- Instructions
- 211 lines in SKILL.md · view on GitHub
Before installing
- Ships executable scripts
Files (91)
- SKILL.md
- shared.json · assets/
- api-failure-message.ts · examples/
- app-providers.tsx · examples/
- app-root.tsx · examples/
- create-device-services.test.ts · examples/composition-root/
- create-device-services.ts · examples/composition-root/
- create-test-services.ts · examples/composition-root/
- services-context.tsx · examples/composition-root/
- environment.ts · examples/
- root-layout.tsx · examples/
- task-list-state.ts · examples/
- task-pages.ts · examples/
- use-tasks-model.test.tsx · examples/
- use-tasks-model.ts · examples/
- api-client-and-transport.md · references/
- checker-rules.md · references/
- mutations-and-optimistic-updates.md · references/
- online-focus-and-offline.md · references/
- queries-and-cache.md · references/
- schemas-and-failures.md · references/
- testing-server-state.md · references/
- check-lib.mjs · scripts/
- check-server-state.mjs · scripts/
- api-rules.mjs · scripts/lib/
- query-rules.mjs · scripts/lib/
- server-state-source.mjs · scripts/lib/
- source-scan.mjs · scripts/lib/
- selftest.mjs · scripts/
- api-client.ts · templates/src/api/
- api-error.test.ts · templates/src/api/
- api-error.ts · templates/src/api/
- api-failure.test.ts · templates/src/api/
- api-failure.ts · templates/src/api/
- api-query-options.ts · templates/src/api/
- build-url.test.ts · templates/src/api/
- build-url.ts · templates/src/api/
- create-query-client.test.ts · templates/src/api/
- create-query-client.ts · templates/src/api/
- fetch-api-client.test.ts · templates/src/api/
- fetch-api-client.ts · templates/src/api/
- query-managers.test.ts · templates/src/api/
- query-managers.ts · templates/src/api/
- query-provider.test.tsx · templates/src/api/
- query-provider.tsx · templates/src/api/
- task-keys.test.ts · templates/src/api/tasks/
- task-keys.ts · templates/src/api/tasks/
- task-mutations.test.ts · templates/src/api/tasks/
- task-mutations.ts · templates/src/api/tasks/
- task-queries.test.ts · templates/src/api/tasks/
- task-queries.ts · templates/src/api/tasks/
- task-schemas.test.ts · templates/src/api/tasks/
- task-schemas.ts · templates/src/api/tasks/
- to-api-failure.test.ts · templates/src/api/
- to-api-failure.ts · templates/src/api/
- api-base-url.test.ts · templates/src/config/
- api-base-url.ts · templates/src/config/
- app-variant.test.ts · templates/src/config/
- app-variant.ts · templates/src/config/
- result.test.ts · templates/src/core/
- …and 31 more
SKILL.md (first lines)
# Networking and server state
The app talks to its server through one typed `ApiClient` port whose only transport calls `fetch` with
a deadline, checks every response against a valibot schema and never rejects; server data lives in
TanStack Query's cache, defined once per resource in `src/api/`, read in model hooks, retried only when
a retry can help, kept fresh by React Native focus and connectivity, and honest about offline. A
zero-dependency checker proves it.
## Rules that must hold
1. **One network seam.** `fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource` and `sendBeacon` appear
only in `src/api/`, and `fetch` in exactly one file, `src/api/fetch-api-client.ts`; no HTTP client
package (axios, ky, `expo/fetch` imports). `src/api/api-client.ts` exports `ApiClient`,
`src/api/api-failure.ts` exports `ApiFailure`. Why: one place owns deadlines, validation, auth
headers and the privacy inventory, and lint can ban the rest.
2. **The ApiClient never rejects.** `request()` returns `Result<T, ApiFailure>`: offline, timeouts,
cancellation, HTTP errors and invalid bodies are typed values; every awaited `fetch` and body read
sits inside one `try/catch`; a failure is classified by who aborted (the caller's signal, the
client's own deadline) and by status, never by error text. Why: expo/fetch, React Native's fetch and
Node's fetch word every error differently, and a rejection that escapes crashes a release handler.
3. **Every attempt has a deadline and honours the caller's signal.** The transport aborts each attempt
after `timeoutMs` (default 15 s, body included), links TanStack's `signal`, and every `queryFn` passes
`signal` to the request. Why: Android's OkHttp client has no timeouts, so a stalled request never
ends; TanStack cancels an abandoned fetch only when the `queryFn` read the signal.
4. **Every response passes its schema, tolerantly.** Each request names a valibot `schema`; the
transport `safeParse`s every 2xx body and records an invalid one once (method, path, issue paths,
never the body); no `.json() as T`, no `.json()` or `JSON.parse` outside the transport; response
schemas use `v.object` (never `v.strictObject`) and wrap closed sets in `v.fallback`. Why: a cast
checks nothing, and a server that adds a field or an enum value must not break installed apps.
5. **The base URL comes from config, over HTTPS.** `EXPO_PUBLIC_API_URL` per EAS environment, read by
the app's `src/config` reader and checked by `parseApiBaseUrl` (https outside development); no URL
literal in `src/api/`, no `baseUrl: '<literal>'`, no `NSAllowsArbitraryLoads` or
`usesCleartextTraffic` switch. Why: each variant talks to its own server, and release builds block
cleartext on iOS (ATS) and Android 9+, so a global switch ships to the store.
6. **Queries and mutations are defined once, next to their keys.** Each resource has
`src/api/<resource>/<resource>-keys.ts` (hierarchical, `all` root, `as const`),
`-schemas.ts`, `-queries.ts` (`queryOptions`/`infiniteQueryOptions`) and `-mutations.ts`
(`mutationOptions`); no inline key arrays, no `queryFn`/`mutationFn` or `.request({ method, path })`
elsewhere. Why: one typo in an inline key misses an invalidation, and `queryOptions` ties the key to
the data type for `getQueryData`/`setQueryData` without casts.
7. **Server state is read in the feature's model hook.** TanStack hooks appear only in
`src/features/**/use-*.ts(x)`, `src/hooks/` and `src/api/`; views get plain props; query data is
never copied into a store, `useState` or an effect (the house ESLint config catches the effect copy,
not this checker). Why: two copies drift on every refetch, and the view stays testable without a
client.
8. **One QueryClient per app run, with a failure-aware retry policy.** `createQueryClient` in
`src/api/` is the only `new QueryClient` in app code, called once from AppRoot's `startApp` (a
`useState` initializer, after the data layer and the error sink; never at module level or in a render body); queries retry only retryable failures
(no connection, deadline, 408, 429 after `Retry-After`, 5xx), at most 3 times; mutations retry only
with an `idempotencyKey`; never `retry: true`/`Infinity`/more than 3. Why: TanStack's default spins
about seven seconds on a 404, and a retried POST without a key can create two tasks.
9. **Focus and online come from React Native, through one port.** `connectQueryManagers` feeds
`focusManager` from AppState (`active` only) and `onlineManager` from the `ConnectivityPort`, whose
one expo-network adapter (the only importer of expo-network or NetInfo) is created by the
composition root; `QueryProvider` mounts the client and the wiring in the root providers. Online
means `isConnected` alone; Android's `isInternetReachable` is only the `reachability()` hint for
banners, ads and purchases. Why: React Native has no window events, so without it nothing pauses
offline, resumes on reconnect or refetches on return; and Android marks some working networks
unvalidated for good, where pausing on reachability would keep every request waiting forever.
10. **Every write says what it does to the cache; optimistic writes can roll back.** A mutation
invalidates or sets the keys it changed in `onSuccess`/`onSettled`, or carries
`// no-cache-effect: <why>`; an `onMutate` that writes the cache cancels in-flight queries first,
snapshots, restores the snapshot in `onError` and invalidates in `onSettled`. Why: otherwise lists
show old data, or a refused write stays on screen as if it happened.
11. **Offline pauses, it does not fail.** Keep `networkMode: 'online'` (a local-only query says
`// network-mode-always: <why>`); show cached data with an offline notice, never an error in its
place; a persisted cache sets `buster` and `maxAge`; writes that must survive a kill go to the
outbox (persistence-and-migrations) and sync with their id as `idempotencyKey`. Why: paused work
resumes by itself, and a restored cache in an old shape would skip every schema.
12. **Tests use the seams, never the network.** The transport gets a fake `FetchLike`; everything above
it gets `createFakeApiClient()` from `test/api/`, which checks canned bodies through the request's
schema and rejects unscripted requests; no test replaces the global `fetch` or mocks `expo/fetch`.
Why: a replaced global leaks across tests and proves a seam the app never uses.
13. **The UI shows failures by kind.** Model hooks read `apiFailureOf(error)` and map each `kind`
through one `Record<ApiFailureKind, MessageKey | null>` table; never `error.message`; `cancelled` is
never shown and expected failures are never recorded. Why: a new kind is then a compile error until
it has a message in every locale.
## Workflow
1. Decide whether the app has a server. An app with no network at all follows the `offline-only`
profile of privacy-and-store-declarations instead (the checker prints NOT APPLICABLE there). Read
[references/api-client-and-transport.md](references/api-client-and-transport.md).
2. Install (stack table, verified 2026-10-03): `npx expo install expo-network` and
`npm install -E @tanstack/react-query@5.104.0 valibot@1.5.0`.
3. Copy `templates/src/api/` (with `tasks/`), `templates/src/config/api-base-url.ts` and its test,
`templates/src/services/connectivity/`, and `templates/test/api/`. The synced files
(`src/core/result.ts`, `src/config/app-variant.ts`, `src/services/error-log/*`,
`test/flush-microtasks.ts`) come from their owner skills: copy them only if the app lacks them.
4. Environment: declare `EXPO_PUBLIC_API_URL` in `src/env.d.ts` and read it in the app's
`src/config/environment.ts` as `examples/environment.ts` shows. Setting the value in each EAS
environment (`eas env:set --name EXPO_PUBLIC_API_URL --value <https-url> --environment <env>
--visibility plaintext`) is the developer's step: stop and ask before running it.
5. Composition root: add `api` and `connectivity` to `Services`, `createDeviceServices` and
`createTestServices` as in `examples/composition-root/`.
6. Startup: read the checked environment in the root layout's `decideStartup`
(`examples/root-layout.tsx`), create the client in AppRoot's `startApp` right after the error sink,
and render `QueryProvider` right after `AppDataProvider` inside `AppProviders`
(`examples/app-root.tsx`, `examples/app-providers.tsx`; app-startup-and-bootstrap owns the order).
7. Each resource: replace the Tasks keys, schemas, queries and mutations with the app's own, reading
[references/schemas-and-failures.md](references/schemas-and-failures.md),
[references/queries-and-cache.md](references/queries-and-cache.md) and
[references/mutations-and-optimistic-updates.md](references/mutations-and-optimistic-updates.md).
Paged lists: `examples/task-pages.ts`.
8. Model hooks: read server state as `examples/use-tasks-model.ts` does, turn query state into a view
state (`examples/task-list-state.ts`) and failures into message keys
(`examples/api-failure-message.ts`; add the keys to every catalog with i18n-strings-and-catalogs).
9. Offline needs (a persisted cache, an outbox, sign-out clearing): read
[references/online-focus-and-offline.md](references/online-focus-and-offline.md).
10. Tests: follow [references/testing-server-state.md](references/testing-server-state.md); run
`npx jest src/api src/config src/services/connectivity test/api --ci` and `npm run typecheck`.
11. Run `node ${CLAUDE_SKILL_DIR}/scripts/check-server-state.mjs .` from the repo root; fix each `FAIL`
line (rule ids in [references/checker-rules.md](references/checker-rules.md)) and rerun until
`RESULT: PASS`.
## Definition of done
- [ ] `fetch` appears only in `src/api/fetch-api-client.ts`; no other network API outside `src/api/`; no HTTP client package.
- [ ] `request()` never rejects: fetch and the body read sit in one `try/catch`; each attempt has a deadline linked to the caller's signal; every `queryFn` passes `signal`.From zakariaf/ReactNative-Skills, MIT.
More from zakariaf/ReactNative-Skills
- 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
android-release-play
zakariaf
Ships Android App Bundles to Google Play: upload key, Play App Signing, AAB gate, versionCode, tracks, rollout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
architecture-and-boundaries
zakariaf
Places app code in the src/ layout and checks import boundaries: one-way zones, pure core, ports with fakes, one composition root.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
async-safety
zakariaf
Prevents silent async bugs: effect races, stale results, lost promises, leaked listeners and timers, AbortSignal gaps.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
audio-and-haptics-feedback
zakariaf
Builds SFX and haptics behind ports.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
auth-and-secure-storage
zakariaf
Builds sign-in - tokens in expo-secure-store, shared refresh, protected routes, OAuth PKCE, Apple sign-in.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
background-tasks
zakariaf
Builds expo-background-task jobs: idempotent, budgeted runs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9
Signals
- Stars
- 11
- Forks
- 2
- Open issues
- 0
- Last commit (this folder)
- Oct 17, 2026 (11 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/zakariaf/ReactNative-Skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from zakariaf
- 0.606score
admob-ads-and-consent
zakariaf
Builds AdMob ads behind a port - banners, interstitials, rewarded, UMP consent then ATT, pacing, ADS_MODE test IDs.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
accessibility-as-code
zakariaf
Enforces a11y labels, 44pt targets, 200% text, VoiceOver, TalkBack.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
canvas-gestures-and-hit-testing
zakariaf
Turns canvas taps, swipes, drags and aim into typed intents: one gesture module, hit-testing on the drawn layout.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
adaptive-layout
zakariaf
Lays screens out by window, not device: breakpoints, master-detail panes, readable widths, grids.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
debug-menu-and-test-hooks
zakariaf
Gates test-only code behind one literal TEST_ONLY switch: debug menu, debug link, simulated clock and offline.
SkillAFreshness: A★ 11Claude Code +9 - 0.606score
analytics-and-consent
zakariaf
Gates analytics on opt-in consent: no SDK before a yes, typed events without personal data, ATT.
SkillAFreshness: A★ 11Claude Code +9
Cite this page
networking-and-server-state skill. Agent Mart. https://agentmart.dev/skills/zakariaf-networking-and-server-state (last updated 2026-10-24)