Skip to content
cccskills
FreePublished on GitHub

native-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, and Expo Router data loaders (`useLoaderData`).

The English summary is being prepared. Showing the original description.

See how to install

Files included (2)

  • SKILL.md11.4 KB
  • references/expo-router-loaders.md9.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

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+)

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

Common Issues & Solutions

1. Basic Fetch Usage

Simple GET request:

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:

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:

// 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 (
    <QueryClientProvider client={queryClient}>
      <Stack />
    </QueryClientProvider>
  );
}

Fetching data:

import { useQuery } from "@tanstack/react-query";

function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ["user", userId],
    queryFn: () => fetchUser(userId),
  });

  if (isLoading) return <Loading />;
  if (error) return <Error message={error.message} />;

  return <Profile user={data} />;
}

Mutations:

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) => {
    mutation.mutate(data);
  };

  return <Form onSubmit={handleSubmit} isLoading={mutation.isPending} />;
}

3. Error Handling

Comprehensive error handling:

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:

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:

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:

let isRefreshing = false;
let refreshPromise: Promise<string> | null = null;

const getValidToken = async (): Promise<string> => {
  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

Check network status:

import NetInfo from "@react-native-community/netinfo";

// Hook for network status
function useNetworkStatus() {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    return NetInfo.addEventListener((state) => {
      setIsOnline(state.isConnected ?? true);
    });
  }, []);

  return isOnline;
}

Offline-first with React Query:

import { onlineManager } from "@tanstack/react-query";
import NetInfo from "@react-native-community/netinfo";

// Sync React Query with network status
onlineManager.setEventListener((setOnline) => {
  return NetInfo.addEventListener((state) => {
    setOnline(state.isConnected ?? true);
  });
});

// Queries will pause when offline and resume when online

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.

// .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:

// .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:

// 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 <T,>(path: string): Promise<T> => {
    const response = await fetch(`${BASE_URL}${path}`);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  },

  post: async <T,>(path: string, body: unknown): Promise<T> => {
    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:

// types/env.d.ts
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      EXPO_PUBLIC_API_URL: string;
      EXPO_PUBLIC_API_VERSION?: string;
    }
  }
}

export {};

7. Request Cancellation

Cancel on unmount:

useEffect(() => {
  const controller = new AbortController();

  fetch(url, { signal: controller.signal })
    .then((response) => response.json())
    .then(setData)
    .catch((error) => {
      if (error.name !== "AbortError") {
        setError(error);
      }
    });

  return () => controller.abort();
}, [url]);

With React Query (automatic):

// React Query automatically cancels requests when queries are invalidated
// or components unmount

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

const data = await fetch(url).then((r) => r.json());

Right: Check response status

const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();

Wrong: Storing tokens in AsyncStorage

await AsyncStorage.setItem("token", token); // Not secure!

Right: Use SecureStore for sensitive data

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 EXPOPUBLIC env vars with .env.development and .env.production files

User: "Where should I put my API key?" -> Client-safe keys: EXPOPUBLIC 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.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Automates Apple-platform apps (iOS, tvOS, macOS) and Android devices. Use when navigating apps, taking snapshots/screenshots, tapping, typing, scrolling, extracting UI info, collecting logs/network/perf evidence, or planning agent-device CLI commands.

The English summary is being prepared. Showing the original description.

CherryHQ/cherry-studio-app4KUpdated Oct 20, 2026

Complete guide for building beautiful apps with Expo Router. Covers fundamentals, styling, components, navigation, animations, patterns, and native tabs.

The English summary is being prepared. Showing the original description.

CherryHQ/cherry-studio-app4KUpdated Oct 20, 2026

Opt-in evidence-first causal diagnosis for bugs, browser or app/device failures, flaky behavior, and performance regressions. Activate only when the user explicitly invokes `$diagnose` or explicitly asks to use the named diagnose skill. Do not activate merely because the user mentions a bug, asks why something failed, requests debugging or a fix, or describes unexpected behavior. Once explicitly invoked, locate likely causes, instrument relevant boundaries with extensive structured logging, reproduce the issue, analyze the collected logs, rank causes with confidence scores, and pursue 100% operational confidence while probes can increase confidence.

The English summary is being prepared. Showing the original description.

CherryHQ/cherry-studio-app4KUpdated Oct 20, 2026

Iterative diagnose-and-fix workflow that repeatedly uses the diagnose skill to find a proven problem, plan the smallest credible improvement, implement it, re-diagnose, and continue until no useful fix remains or user intervention is required. Requires the diagnose skill; selected installs must also install diagnose. Use when asked to keep debugging, fix and verify, iterate until satisfied, improve after diagnosis, or run a diagnosis/fix/verification loop.

The English summary is being prepared. Showing the original description.

CherryHQ/cherry-studio-app4KUpdated Oct 20, 2026

Check the health of published EAS Updates: crash rates, install/launch counts, unique users, payload size, and the split between embedded and OTA users per channel. Use when the user asks how an update is performing, whether a rollout is healthy, how many users are on the embedded build vs OTA, or wants to gate CI on update health.

The English summary is being prepared. Showing the original description.

CherryHQ/cherry-studio-app4KUpdated Oct 20, 2026

Helps understand and write EAS workflow YAML files for Expo projects. Use this skill when the user asks about CI/CD or workflows in an Expo or EAS context, mentions .eas/workflows/, or wants help with EAS build pipelines or deployment automation.

The English summary is being prepared. Showing the original description.

CherryHQ/cherry-studio-app4KUpdated Oct 20, 2026

See all skills by CherryHQ

Report a problem with this skill