Skip to main content
ZICQ

Skills ZICQ category:Agent Workflows expo-data-fetching

Expo Data Fetching

Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, loading/empty/error screen states, and Expo Router data loaders (`useLoaderData`).

33055 installs

Official URL:skills.sh

What this skill does

Intro in this page language first. The official description stays in its original wording; we do not rewrite SKILL.md.

What it does

Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, loading/empty/error screen states, and Expo Router data loaders (`useLoaderData`).

When to use it

Use this skill when:

How agents load it

Per Agent Skills progressive disclosure: name and description load at startup (~100 tokens); the full SKILL.md body loads when the skill activates; scripts/, references/, and assets/ load only as needed. This file's sections: Expo Networking; References; When to Use; Preferences; Every Screen Has Four States; Common Issues & Solutions.

File analysis

File analysis: besides SKILL.md, the body references references/offline-and-cancellation.md, references/expo-router-loaders.md. Those resources load on demand.

Expo NetworkingReferencesWhen to UsePreferencesEvery Screen Has Four StatesCommon Issues & Solutions1. Basic Fetch Usage2. React Query (TanStack Query)3. Error Handling4. Authentication5. Offline Support6. Environment Variables

· License:MIT

Source category:skills.sh agent-skill

SKILL.md & Agent activation

Official spec ↗
name
expo-data-fetching
description
Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, loading/empty/error screen states, and Expo Router data loaders (`useLoaderData`).
License
MIT
  1. DiscoverThe client exposes names and descriptions to the agent.
  2. ActivateYour request or the task context selects the skill and loads its instructions.
  3. Load resourcesReferenced scripts, documentation and assets are used when needed.
Files referenced by the instructions · 2
  • references/offline-and-cancellation.md
  • references/expo-router-loaders.md

These paths are extracted from the text. Check the upstream package to verify the files exist.

Invocation syntax and available tools depend on your Agent client. Client integration guide ↗

Install this skill

Skills CLI ↗

Choose the target agent and installation scope, keep referenced package files, then verify the skill appears in the client's catalog.

This skill references supporting files. Retrieve the complete directory from the source; copying SKILL.md alone may leave missing dependencies.

Ask your Agent to install

Copy these instructions to a compatible agent and confirm the target directory matches your client.

Install the agent skill "expo-data-fetching" into my project. The full SKILL.md and official description are at https://zicq.com/en/skills/skl-5e2b98fe533c248f-Expo-Data-Fetching.html
Save it as .cursor/skills/expo-data-fetching/SKILL.md or .claude/skills/expo-data-fetching/SKILL.md and keep the frontmatter name and description exactly as-is.
This skill also ships scripts/, references/, or assets/ — fetch the whole folder from https://github.com/expo/skills instead of creating only a SKILL.md.

Full package on GitHub ↗

Install from the terminal · Skills CLI

Requires Node.js and npx. First inspect the repository's skill list to confirm the name.

npx skills add 'https://github.com/expo/skills' --list

npx skills add 'https://github.com/expo/skills' --skill 'expo-data-fetching'

The CLI lets you choose the agent interactively. The default scope is the project; use -g for user scope. Confirm package availability with the discovery command, then use npx skills list to inspect installed skills.

Readable layout
--- name: expo-data-fetching description: Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, loading/empty/error screen states, and Expo Router data loaders (`useLoaderData`). license: MIT --- # Expo Networking **You MUST use this skill for ANY networking work including API requests, data fetching, caching, or network debugging.** ## References Consult these resources as needed: ``` references/ expo-router-loaders.md Route-level data loading with Expo Router loaders (web, SDK 55+) offline-and-cancellation.md NetInfo network status, offline-first React Query, AbortController ``` ## When to Use Use this skill when: - Implementing API requests - Setting up data fetching (React Query, SWR) - Using Expo Router data loaders (`useLoaderData`, web SDK 55+) - Debugging network failures - Implementing caching strategies - Handling offline scenarios - Authentication/token management - Configuring API URLs and environment variables ## Preferences - Avoid axios, prefer expo/fetch ## Every Screen Has Four States Design **loading**, **error**, **empty**, and **content** for screens that load data. These can overlap: a refresh error should coexist with cached content. - **Loading ≠ empty.** Empty means *resolved with zero items*, not missing data. Handle initial loading, failure, and hydration before checking list length. In TanStack Query v5, `isLoading` means the first fetch is running; a disabled or offline-paused query can have no data without being loading. Show the prerequisite or offline state in that case. - **Empty is a designed state, not a blank list.** Use `ListEmptyComponent` on FlatList/FlashList: explain why it is empty and offer the relevant next action. "No items yet" can offer Create; "No results" should offer changing or clearing the search/filter. - **Refetches keep stale content.** Render cached `data` even if a refresh fails, with a nonblocking error and retry. Use `isLoading` for first-fetch spinners and `isFetching` for background activity; prefer a skeleton for a slow initial load with a known layout, and `RefreshControl` for user-initiated refresh. - **Gate on hydration.** When initial UI or a redirect depends on persisted state (auth token, onboarding flag), the root layout renders nothing - or the splash - until that state has loaded. Deciding on unhydrated state flashes the wrong screen on every cold start and misroutes deep links that arrive before hydration. **Saves preserve work.** While a mutation is pending, disable repeat submission. On failure, retain the draft, show an inline error, and let the user retry; clear or dismiss only after success. If updating optimistically, restore the previous value or mark the edit as unsynced on failure. Verify with a failed save followed by retry. ## Common Issues & Solutions ### 1. Basic Fetch Usage **Simple GET request**: ```tsx const fetchUser = async (userId: string) => { const response = await fetch(`https://api.example.com/users/${userId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }; ``` **POST request with body**: ```tsx const createUser = async (userData: UserData) => { const response = await fetch("https://api.example.com/users", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(userData), }); if (!response.ok) { const error = await response.json(); throw new Error(error.message); } return response.json(); }; ``` --- ### 2. React Query (TanStack Query) **Setup**: ```tsx // app/_layout.tsx import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutes retry: 2, }, }, }); export default function RootLayout() { return ( ); } ``` **Fetching data**: ```tsx import { useQuery } from "@tanstack/react-query"; function UserProfile({ userId }: { userId: string }) { const { data, fetchStatus, error, refetch } = useQuery({ queryKey: ["user", userId], queryFn: () => fetchUser(userId), }); if (data === undefined) { if (error) return refetch()} />; if (fetchStatus === "paused") return ; return ; } return ( <> {error && refetch()} />} {data === null ? : } ); } ``` **Mutations**: ```tsx import { useMutation, useQueryClient } from "@tanstack/react-query"; function CreateUserForm() { const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: createUser, onSuccess: () => { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: ["users"] }); }, }); const handleSubmit = (data: UserData) => { if (mutation.isPending) return; mutation.mutate(data); }; // Form keeps its draft on error and disables Submit while isLoading. return ; } ``` --- ### 3. Error Handling **Comprehensive error handling**: ```tsx class ApiError extends Error { constructor(message: string, public status: number, public code?: string) { super(message); this.name = "ApiError"; } } const fetchWithErrorHandling = async (url: string, options?: RequestInit) => { try { const response = await fetch(url, options); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new ApiError( error.message || "Request failed", response.status, error.code ); } return response.json(); } catch (error) { if (error instanceof ApiError) { throw error; } // Network error (no internet, timeout, etc.) throw new ApiError("Network error", 0, "NETWORK_ERROR"); } }; ``` **Retry logic**: ```tsx const fetchWithRetry = async ( url: string, options?: RequestInit, retries = 3 ) => { for (let i = 0; i < retries; i++) { try { return await fetchWithErrorHandling(url, options); } catch (error) { if (i === retries - 1) throw error; // Exponential backoff await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000)); } } }; ``` --- ### 4. Authentication **Token management**: ```tsx import * as SecureStore from "expo-secure-store"; const TOKEN_KEY = "auth_token"; export const auth = { getToken: () => SecureStore.getItemAsync(TOKEN_KEY), setToken: (token: string) => SecureStore.setItemAsync(TOKEN_KEY, token), removeToken: () => SecureStore.deleteItemAsync(TOKEN_KEY), }; // Authenticated fetch wrapper const authFetch = async (url: string, options: RequestInit = {}) => { const token = await auth.getToken(); return fetch(url, { ...options, headers: { ...options.headers, Authorization: token ? `Bearer ${token}` : "", }, }); }; ``` **Token refresh**: ```tsx let isRefreshing = false; let refreshPromise: Promise | null = null; const getValidToken = async (): Promise => { const token = await auth.getToken(); if (!token || isTokenExpired(token)) { if (!isRefreshing) { isRefreshing = true; refreshPromise = refreshToken().finally(() => { isRefreshing = false; refreshPromise = null; }); } return refreshPromise!; } return token; }; ``` --- ### 5. Offline Support Network-status detection with NetInfo and offline-first React Query setup: see [./references/offline-and-cancellation.md](./references/offline-and-cancellation.md). --- ### 6. Environment Variables **Using environment variables for API configuration**: Expo supports environment variables with the `EXPO_PUBLIC_` prefix. These are inlined at build time and available in your JavaScript code. ```tsx // .env EXPO_PUBLIC_API_URL=https://api.example.com EXPO_PUBLIC_API_VERSION=v1 // Usage in code const API_URL = process.env.EXPO_PUBLIC_API_URL; const fetchUsers = async () => { const response = await fetch(`${API_URL}/users`); return response.json(); }; ``` **Environment-specific configuration**: ```tsx // .env.development EXPO_PUBLIC_API_URL=http://localhost:3000 // .env.production EXPO_PUBLIC_API_URL=https://api.production.com ``` **Creating an API client with environment config**: ```tsx // api/client.ts const BASE_URL = process.env.EXPO_PUBLIC_API_URL; if (!BASE_URL) { throw new Error("EXPO_PUBLIC_API_URL is not defined"); } export const apiClient = { get: async (path: string): Promise => { const response = await fetch(`${BASE_URL}${path}`); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); }, post: async (path: string, body: unknown): Promise => { const response = await fetch(`${BASE_URL}${path}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); }, }; ``` **Important notes**: - Only variables prefixed with `EXPO_PUBLIC_` are exposed to the client bundle - Never put secrets (API keys with write access, database passwords) in `EXPO_PUBLIC_` variables—they're visible in the built app - Environment variables are inlined at **build time**, not runtime - Restart the dev server after changing `.env` files - For server-side secrets in API routes, use variables without the `EXPO_PUBLIC_` prefix **TypeScript support**: ```tsx // types/env.d.ts declare global { namespace NodeJS { interface ProcessEnv { EXPO_PUBLIC_API_URL: string; EXPO_PUBLIC_API_VERSION?: string; } } } export {}; ``` --- ### 7. Request Cancellation AbortController on unmount (React Query cancels automatically): see [./references/offline-and-cancellation.md](./references/offline-and-cancellation.md). --- ## Decision Tree ``` User asks about networking |-- Route-level data loading (web, SDK 55+)? | \-- Expo Router loaders — see references/expo-router-loaders.md | |-- Basic fetch? | \-- Use fetch API with error handling | |-- Need caching/state management? | |-- Complex app -> React Query (TanStack Query) | \-- Simpler needs -> SWR or custom hooks | |-- Authentication? | |-- Token storage -> expo-secure-store | \-- Token refresh -> Implement refresh flow | |-- Error handling? | |-- Network errors -> Check connectivity first | |-- HTTP errors -> Parse response, throw typed errors | \-- Retries -> Exponential backoff | |-- Offline support? | |-- Check status -> NetInfo | \-- Queue requests -> React Query persistence | |-- Environment/API config? | |-- Client-side URLs -> EXPO_PUBLIC_ prefix in .env | |-- Server secrets -> Non-prefixed env vars (API routes only) | \-- Multiple environments -> .env.development, .env.production | \-- Performance? |-- Caching -> React Query with staleTime |-- Deduplication -> React Query handles this \-- Cancellation -> AbortController or React Query ``` ## Common Mistakes **Wrong: No error handling** ```tsx const data = await fetch(url).then((r) => r.json()); ``` **Right: Check response status** ```tsx const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); ``` **Wrong: Storing tokens in AsyncStorage** ```tsx await AsyncStorage.setItem("token", token); // Not secure! ``` **Right: Use SecureStore for sensitive data** ```tsx await SecureStore.setItemAsync("token", token); ``` ## Example Invocations User: "How do I make API calls in React Native?" -> Use fetch, wrap with error handling User: "Should I use React Query or SWR?" -> React Query for complex apps, SWR for simpler needs User: "My app needs to work offline" -> Use NetInfo for status, React Query persistence for caching User: "How do I handle authentication tokens?" -> Store in expo-secure-store, implement refresh flow User: "API calls are slow" -> Check caching strategy, use React Query staleTime User: "How do I configure different API URLs for dev and prod?" -> Use `EXPO_PUBLIC_` env vars with .env.development and .env.production files User: "Where should I put my API key?" -> Client-safe keys: `EXPO_PUBLIC_` in .env. Secret keys: non-prefixed env vars in API routes only User: "How do I load data for a page in Expo Router?" -> See references/expo-router-loaders.md for route-level loaders (web, SDK 55+). For native, use React Query or fetch. ## Submitting Feedback You can voluntarily report errors, misleading or outdated information in this skill, so Expo can improve it: ```bash npx --yes @expo/agent-cli@latest feedback --category skills --subject "expo-data-fetching" --message "" ``` Use `--category agent-cli` for a problem with the agent CLI, and `--category evals` when an agent repeatedly fails at an Expo task. Run `feedback --help` for subjects and the data that the command sends. Each report goes to Expo with project and environment details. Show the user the message, and send it only if the user agrees. If you cannot ask the user, do not send it. Do not include secrets or personal data.

Related skills

Agent Workflows

Skill Creator

Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that exte…

Agent Workflows

Clawdhub

Use the ClawdHub CLI to search, install, update, and publish agent skills from clawdhub.com. Use when you need to fetch new skills on the fl…

Agent Workflows

Agent Team Orchestration

Orchestrate multi-agent teams with defined roles, task lifecycles, handoff protocols, and review workflows. Use when: (1) Setting up a team …

Agent Workflows

Superpowers

Spec-first, TDD, subagent-driven software development workflow. Use when: (1) building any new feature or app — triggers brainstorm → plan →…