ReadyNative

TanStack Query

Set up TanStack Query for data in an Expo app with ReadyNative: server state + cache · Expo Go OK

Expo Go: yes

Runs in Expo Go; no dev build needed for this module.

TanStack Query is the default data layer: a cache, retries and refetch-on-foreground for REST-shaped APIs, wired up in src/lib/query.tsx. I'd pick it over swr when you want mutations, query invalidation and devtool-grade cache control, and over apollo whenever your backend isn't GraphQL. It comes with src/lib/api/client.ts - a small fetchJson wrapper that resolves relative paths against EXPO_PUBLIC_API_URL - so your hooks stay free of URL plumbing.

Setup

bun run setup --data react-query
  1. Put your API base URL in .env as EXPO_PUBLIC_API_URL, for example https://jsonplaceholder.typicode.com. It's optional - the example query stays disabled until you set it.

Setup adds src/lib/query.tsx (the queryClient with retry 1, staleTime 30s, gcTime 5min, and QueryProvider at order 20, refetching on AppState foreground), src/lib/api/client.ts and a test for the fetch client. With --with-examples you also keep the example hook src/hooks/use-example-query.ts and the /examples/query route with its screen.

KeyRequiredWhat it is
EXPO_PUBLIC_API_URLnoBase URL that relative fetchJson paths resolve against

Usage

Write a hook per query and let fetchJson handle the URL and errors:

import { useQuery } from "@tanstack/react-query";
import { fetchJson } from "@/lib/api/client";

export function useMe() {
  return useQuery({ queryKey: ["me"], queryFn: () => fetchJson<Me>("/me") });
}

fetchJson takes a JSON body through init.json, times out, and throws ApiError on a non-2xx response - so mutations read the same way:

const save = useMutation({
  mutationFn: (input: Draft) => fetchJson<Note>("/notes", { method: "POST", json: input }),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ["notes"] }),
});

With --with-examples, src/hooks/use-example-query.ts is the shape to copy: it gates itself with enabled: Boolean(env.API_URL) so a fresh template makes no network calls.

On every sign-out and account deletion wipeLocalData() runs the onWipeLocalData hooks, and src/lib/query.tsx registers queryClient.clear() there, so the next user never sees the previous user's cached responses.

Gotchas

  • Expo Go works - it's pure JS.
  • No NetInfo is bundled, so onlineManager keeps the library default. Add @react-native-community/netinfo and call onlineManager.setEventListener in src/lib/query.tsx if you want offline-aware retries.
  • Swapping stacks: bun run setup --data none (or --data swr / --data apollo) removes the files, dependency, provider and env key. With the examples, /examples/query then falls back to the core "Module not installed" screen. src/lib/api/client.ts is byte-identical in the swr module, so moving between the two doesn't churn it.

Remove it

While modules/ exists (a tree set up with --keep-modules), setup does all of it:

bun run setup --data none --yes --keep-modules

In a finalized tree setup is a stub, so you undo it by hand. Here is everything this module added:

  1. Delete the files that are still there (the demo screens are gone already unless you set up with --with-examples): src/app/examples/query.tsx, src/hooks/use-example-query.ts, src/lib/api/__tests__/client.test.ts, src/lib/api/client.ts, src/lib/query.tsx, src/screens/examples/query-example-screen.tsx.
  2. Replace, don't delete src/hooks/use-forecast.ts: core code imports it, so swap in the no-op version from modules/data/none/files/ of a fresh clone of your tier repo - same exports, nothing behind them.
  3. Uninstall the dependencies: bun remove @tanstack/react-query.
  4. Unwrap the provider: delete <QueryProvider> and its import from src/providers.tsx.
  5. Remove the env keys EXPO_PUBLIC_API_URL from .env, .env.example and your EAS environment, and EXPO_PUBLIC_API_URL from src/lib/env.ts.
  6. Check it: bun run typecheck and bun run lint point at anything that still imports the removed files; bun run gen:graph refreshes docs/ARCHITECTURE.md.

Reference

Everything below is generated from modules/data/react-query/module.json - the same file bun run setup reads, so it is what actually lands in your repo.

Install

bun run setup --data react-query

Module id: data/react-query (the default for this category).

Dependencies

PackageVersionKind
@tanstack/react-query^5.103.2dependency

Environment keys

KeyRequiredServer-onlyExampleDocs
EXPO_PUBLIC_API_URLnonohttps://jsonplaceholder.typicode.com-

Keys go in .env (see .env.example). Required keys are checked by bun run doctor; Server-only keys have no EXPO_PUBLIC_ prefix, are read only by API routes and never reach the bundle.

Providers

Rendered in src/providers.tsx (lower order = outermost):

OrderProviderFrom
20QueryProvider@/lib/query

Files

7 files copied to the project root
  • src/app/examples/query.tsx
  • src/hooks/use-example-query.ts
  • src/hooks/use-forecast.ts
  • src/lib/api/__tests__/client.test.ts
  • src/lib/api/client.ts
  • src/lib/query.tsx
  • src/screens/examples/query-example-screen.tsx

On this page

Get ReadyNative