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- Put your API base URL in
.envasEXPO_PUBLIC_API_URL, for examplehttps://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.
| Key | Required | What it is |
|---|---|---|
EXPO_PUBLIC_API_URL | no | Base 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
onlineManagerkeeps the library default. Add@react-native-community/netinfoand callonlineManager.setEventListenerinsrc/lib/query.tsxif 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/querythen falls back to the core "Module not installed" screen.src/lib/api/client.tsis byte-identical in theswrmodule, 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-modulesIn a finalized tree setup is a stub, so you undo it by hand. Here is everything this module added:
- 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. - Replace, don't delete
src/hooks/use-forecast.ts: core code imports it, so swap in the no-op version frommodules/data/none/files/of a fresh clone of your tier repo - same exports, nothing behind them. - Uninstall the dependencies:
bun remove @tanstack/react-query. - Unwrap the provider: delete
<QueryProvider>and its import fromsrc/providers.tsx. - Remove the env keys
EXPO_PUBLIC_API_URLfrom.env,.env.exampleand your EAS environment, andEXPO_PUBLIC_API_URLfromsrc/lib/env.ts. - Check it:
bun run typecheckandbun run lintpoint at anything that still imports the removed files;bun run gen:graphrefreshesdocs/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-queryModule id: data/react-query (the default for this category).
Dependencies
| Package | Version | Kind |
|---|---|---|
@tanstack/react-query | ^5.103.2 | dependency |
Environment keys
| Key | Required | Server-only | Example | Docs |
|---|---|---|---|---|
EXPO_PUBLIC_API_URL | no | no | https://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):
| Order | Provider | From |
|---|---|---|
| 20 | QueryProvider | @/lib/query |
Files
7 files copied to the project root
src/app/examples/query.tsxsrc/hooks/use-example-query.tssrc/hooks/use-forecast.tssrc/lib/api/__tests__/client.test.tssrc/lib/api/client.tssrc/lib/query.tsxsrc/screens/examples/query-example-screen.tsx