[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F44FADYQXNGKQQZ95FYW1E":3,"skill-same:01M6F44FADYQXNGKQQZ95FYW1E":78},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":28,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":58,"repositoryHtmlUrl":59,"repositoryHomepageUrl":60,"ownerHtmlUrl":61,"ownerBlogUrl":6,"ownerDescription":62,"updatedAt":63,"install":64,"resources":77},"01M6F44FADYQXNGKQQZ95FYW1E","native-data-fetching",null,"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).","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fexpo\u002Fskills\u002Fnative-data-fetching\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"native","native data fetching","data","fetch","expo router","error","caching","query","call","expo","router","use","support","api","network","swr","offline","error handling","debugging","react","2026-11-10 10:12:30",false,"---\nname: native-data-fetching\ndescription: 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).\nversion: 1.0.0\nlicense: MIT\n---\n\n# Expo Networking\n\n**You MUST use this skill for ANY networking work including API requests, data fetching, caching, or network debugging.**\n\n## References\n\nConsult these resources as needed:\n\n```\nreferences\u002F\n  expo-router-loaders.md   Route-level data loading with Expo Router loaders (web, SDK 55+)\n```\n\n## When to Use\n\nUse this skill when:\n\n- Implementing API requests\n- Setting up data fetching (React Query, SWR)\n- Using Expo Router data loaders (`useLoaderData`, web SDK 55+)\n- Debugging network failures\n- Implementing caching strategies\n- Handling offline scenarios\n- Authentication\u002Ftoken management\n- Configuring API URLs and environment variables\n\n## Preferences\n\n- Avoid axios, prefer expo\u002Ffetch\n\n## Common Issues & Solutions\n\n### 1. Basic Fetch Usage\n\n**Simple GET request**:\n\n```tsx\nconst fetchUser = async (userId: string) => {\n  const response = await fetch(`https:\u002F\u002Fapi.example.com\u002Fusers\u002F${userId}`);\n\n  if (!response.ok) {\n    throw new Error(`HTTP error! status: ${response.status}`);\n  }\n\n  return response.json();\n};\n```\n\n**POST request with body**:\n\n```tsx\nconst createUser = async (userData: UserData) => {\n  const response = await fetch(\"https:\u002F\u002Fapi.example.com\u002Fusers\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application\u002Fjson\",\n      Authorization: `Bearer ${token}`,\n    },\n    body: JSON.stringify(userData),\n  });\n\n  if (!response.ok) {\n    const error = await response.json();\n    throw new Error(error.message);\n  }\n\n  return response.json();\n};\n```\n\n---\n\n### 2. React Query (TanStack Query)\n\n**Setup**:\n\n```tsx\n\u002F\u002F app\u002F_layout.tsx\nimport { QueryClient, QueryClientProvider } from \"@tanstack\u002Freact-query\";\n\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      staleTime: 1000 * 60 * 5, \u002F\u002F 5 minutes\n      retry: 2,\n    },\n  },\n});\n\nexport default function RootLayout() {\n  return (\n    \u003CQueryClientProvider client={queryClient}>\n      \u003CStack \u002F>\n    \u003C\u002FQueryClientProvider>\n  );\n}\n```\n\n**Fetching data**:\n\n```tsx\nimport { useQuery } from \"@tanstack\u002Freact-query\";\n\nfunction UserProfile({ userId }: { userId: string }) {\n  const { data, isLoading, error, refetch } = useQuery({\n    queryKey: [\"user\", userId],\n    queryFn: () => fetchUser(userId),\n  });\n\n  if (isLoading) return \u003CLoading \u002F>;\n  if (error) return \u003CError message={error.message} \u002F>;\n\n  return \u003CProfile user={data} \u002F>;\n}\n```\n\n**Mutations**:\n\n```tsx\nimport { useMutation, useQueryClient } from \"@tanstack\u002Freact-query\";\n\nfunction CreateUserForm() {\n  const queryClient = useQueryClient();\n\n  const mutation = useMutation({\n    mutationFn: createUser,\n    onSuccess: () => {\n      \u002F\u002F Invalidate and refetch\n      queryClient.invalidateQueries({ queryKey: [\"users\"] });\n    },\n  });\n\n  const handleSubmit = (data: UserData) => {\n    mutation.mutate(data);\n  };\n\n  return \u003CForm onSubmit={handleSubmit} isLoading={mutation.isPending} \u002F>;\n}\n```\n\n---\n\n### 3. Error Handling\n\n**Comprehensive error handling**:\n\n```tsx\nclass ApiError extends Error {\n  constructor(message: string, public status: number, public code?: string) {\n    super(message);\n    this.name = \"ApiError\";\n  }\n}\n\nconst fetchWithErrorHandling = async (url: string, options?: RequestInit) => {\n  try {\n    const response = await fetch(url, options);\n\n    if (!response.ok) {\n      const error = await response.json().catch(() => ({}));\n      throw new ApiError(\n        error.message || \"Request failed\",\n        response.status,\n        error.code\n      );\n    }\n\n    return response.json();\n  } catch (error) {\n    if (error instanceof ApiError) {\n      throw error;\n    }\n    \u002F\u002F Network error (no internet, timeout, etc.)\n    throw new ApiError(\"Network error\", 0, \"NETWORK_ERROR\");\n  }\n};\n```\n\n**Retry logic**:\n\n```tsx\nconst fetchWithRetry = async (\n  url: string,\n  options?: RequestInit,\n  retries = 3\n) => {\n  for (let i = 0; i \u003C retries; i++) {\n    try {\n      return await fetchWithErrorHandling(url, options);\n    } catch (error) {\n      if (i === retries - 1) throw error;\n      \u002F\u002F Exponential backoff\n      await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000));\n    }\n  }\n};\n```\n\n---\n\n### 4. Authentication\n\n**Token management**:\n\n```tsx\nimport * as SecureStore from \"expo-secure-store\";\n\nconst TOKEN_KEY = \"auth_token\";\n\nexport const auth = {\n  getToken: () => SecureStore.getItemAsync(TOKEN_KEY),\n  setToken: (token: string) => SecureStore.setItemAsync(TOKEN_KEY, token),\n  removeToken: () => SecureStore.deleteItemAsync(TOKEN_KEY),\n};\n\n\u002F\u002F Authenticated fetch wrapper\nconst authFetch = async (url: string, options: RequestInit = {}) => {\n  const token = await auth.getToken();\n\n  return fetch(url, {\n    ...options,\n    headers: {\n      ...options.headers,\n      Authorization: token ? `Bearer ${token}` : \"\",\n    },\n  });\n};\n```\n\n**Token refresh**:\n\n```tsx\nlet isRefreshing = false;\nlet refreshPromise: Promise\u003Cstring> | null = null;\n\nconst getValidToken = async (): Promise\u003Cstring> => {\n  const token = await auth.getToken();\n\n  if (!token || isTokenExpired(token)) {\n    if (!isRefreshing) {\n      isRefreshing = true;\n      refreshPromise = refreshToken().finally(() => {\n        isRefreshing = false;\n        refreshPromise = null;\n      });\n    }\n    return refreshPromise!;\n  }\n\n  return token;\n};\n```\n\n---\n\n### 5. Offline Support\n\n**Check network status**:\n\n```tsx\nimport NetInfo from \"@react-native-community\u002Fnetinfo\";\n\n\u002F\u002F Hook for network status\nfunction useNetworkStatus() {\n  const [isOnline, setIsOnline] = useState(true);\n\n  useEffect(() => {\n    return NetInfo.addEventListener((state) => {\n      setIsOnline(state.isConnected ?? true);\n    });\n  }, []);\n\n  return isOnline;\n}\n```\n\n**Offline-first with React Query**:\n\n```tsx\nimport { onlineManager } from \"@tanstack\u002Freact-query\";\nimport NetInfo from \"@react-native-community\u002Fnetinfo\";\n\n\u002F\u002F Sync React Query with network status\nonlineManager.setEventListener((setOnline) => {\n  return NetInfo.addEventListener((state) => {\n    setOnline(state.isConnected ?? true);\n  });\n});\n\n\u002F\u002F Queries will pause when offline and resume when online\n```\n\n---\n\n### 6. Environment Variables\n\n**Using environment variables for API configuration**:\n\nExpo supports environment variables with the `EXPO_PUBLIC_` prefix. These are inlined at build time and available in your JavaScript code.\n\n```tsx\n\u002F\u002F .env\nEXPO_PUBLIC_API_URL=https:\u002F\u002Fapi.example.com\nEXPO_PUBLIC_API_VERSION=v1\n\n\u002F\u002F Usage in code\nconst API_URL = process.env.EXPO_PUBLIC_API_URL;\n\nconst fetchUsers = async () => {\n  const response = await fetch(`${API_URL}\u002Fusers`);\n  return response.json();\n};\n```\n\n**Environment-specific configuration**:\n\n```tsx\n\u002F\u002F .env.development\nEXPO_PUBLIC_API_URL=http:\u002F\u002Flocalhost:3000\n\n\u002F\u002F .env.production\nEXPO_PUBLIC_API_URL=https:\u002F\u002Fapi.production.com\n```\n\n**Creating an API client with environment config**:\n\n```tsx\n\u002F\u002F api\u002Fclient.ts\nconst BASE_URL = process.env.EXPO_PUBLIC_API_URL;\n\nif (!BASE_URL) {\n  throw new Error(\"EXPO_PUBLIC_API_URL is not defined\");\n}\n\nexport const apiClient = {\n  get: async \u003CT,>(path: string): Promise\u003CT> => {\n    const response = await fetch(`${BASE_URL}${path}`);\n    if (!response.ok) throw new Error(`HTTP ${response.status}`);\n    return response.json();\n  },\n\n  post: async \u003CT,>(path: string, body: unknown): Promise\u003CT> => {\n    const response = await fetch(`${BASE_URL}${path}`, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application\u002Fjson\" },\n      body: JSON.stringify(body),\n    });\n    if (!response.ok) throw new Error(`HTTP ${response.status}`);\n    return response.json();\n  },\n};\n```\n\n**Important notes**:\n\n- Only variables prefixed with `EXPO_PUBLIC_` are exposed to the client bundle\n- Never put secrets (API keys with write access, database passwords) in `EXPO_PUBLIC_` variables—they're visible in the built app\n- Environment variables are inlined at **build time**, not runtime\n- Restart the dev server after changing `.env` files\n- For server-side secrets in API routes, use variables without the `EXPO_PUBLIC_` prefix\n\n**TypeScript support**:\n\n```tsx\n\u002F\u002F types\u002Fenv.d.ts\ndeclare global {\n  namespace NodeJS {\n    interface ProcessEnv {\n      EXPO_PUBLIC_API_URL: string;\n      EXPO_PUBLIC_API_VERSION?: string;\n    }\n  }\n}\n\nexport {};\n```\n\n---\n\n### 7. Request Cancellation\n\n**Cancel on unmount**:\n\n```tsx\nuseEffect(() => {\n  const controller = new AbortController();\n\n  fetch(url, { signal: controller.signal })\n    .then((response) => response.json())\n    .then(setData)\n    .catch((error) => {\n      if (error.name !== \"AbortError\") {\n        setError(error);\n      }\n    });\n\n  return () => controller.abort();\n}, [url]);\n```\n\n**With React Query** (automatic):\n\n```tsx\n\u002F\u002F React Query automatically cancels requests when queries are invalidated\n\u002F\u002F or components unmount\n```\n\n---\n\n## Decision Tree\n\n```\nUser asks about networking\n  |-- Route-level data loading (web, SDK 55+)?\n  |   \\-- Expo Router loaders — see references\u002Fexpo-router-loaders.md\n  |\n  |-- Basic fetch?\n  |   \\-- Use fetch API with error handling\n  |\n  |-- Need caching\u002Fstate management?\n  |   |-- Complex app -> React Query (TanStack Query)\n  |   \\-- Simpler needs -> SWR or custom hooks\n  |\n  |-- Authentication?\n  |   |-- Token storage -> expo-secure-store\n  |   \\-- Token refresh -> Implement refresh flow\n  |\n  |-- Error handling?\n  |   |-- Network errors -> Check connectivity first\n  |   |-- HTTP errors -> Parse response, throw typed errors\n  |   \\-- Retries -> Exponential backoff\n  |\n  |-- Offline support?\n  |   |-- Check status -> NetInfo\n  |   \\-- Queue requests -> React Query persistence\n  |\n  |-- Environment\u002FAPI config?\n  |   |-- Client-side URLs -> EXPO_PUBLIC_ prefix in .env\n  |   |-- Server secrets -> Non-prefixed env vars (API routes only)\n  |   \\-- Multiple environments -> .env.development, .env.production\n  |\n  \\-- Performance?\n      |-- Caching -> React Query with staleTime\n      |-- Deduplication -> React Query handles this\n      \\-- Cancellation -> AbortController or React Query\n```\n\n## Common Mistakes\n\n**Wrong: No error handling**\n\n```tsx\nconst data = await fetch(url).then((r) => r.json());\n```\n\n**Right: Check response status**\n\n```tsx\nconst response = await fetch(url);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst data = await response.json();\n```\n\n**Wrong: Storing tokens in AsyncStorage**\n\n```tsx\nawait AsyncStorage.setItem(\"token\", token); \u002F\u002F Not secure!\n```\n\n**Right: Use SecureStore for sensitive data**\n\n```tsx\nawait SecureStore.setItemAsync(\"token\", token);\n```\n\n## Example Invocations\n\nUser: \"How do I make API calls in React Native?\"\n-> Use fetch, wrap with error handling\n\nUser: \"Should I use React Query or SWR?\"\n-> React Query for complex apps, SWR for simpler needs\n\nUser: \"My app needs to work offline\"\n-> Use NetInfo for status, React Query persistence for caching\n\nUser: \"How do I handle authentication tokens?\"\n-> Store in expo-secure-store, implement refresh flow\n\nUser: \"API calls are slow\"\n-> Check caching strategy, use React Query staleTime\n\nUser: \"How do I configure different API URLs for dev and prod?\"\n-> Use EXPO*PUBLIC* env vars with .env.development and .env.production files\n\nUser: \"Where should I put my API key?\"\n-> Client-safe keys: EXPO*PUBLIC* in .env. Secret keys: non-prefixed env vars in API routes only\n\nUser: \"How do I load data for a page in Expo Router?\"\n-> See references\u002Fexpo-router-loaders.md for route-level loaders (web, SDK 55+). For native, use React Query or fetch.\n\n","# Expo Networking\n\n**You MUST use this skill for ANY networking work including API requests, data fetching, caching, or network debugging.**\n\n## References\n\nConsult these resources as needed:\n\n```\nreferences\u002F\n  expo-router-loaders.md   Route-level data loading with Expo Router loaders (web, SDK 55+)\n```\n\n## When to Use\n\nUse this skill when:\n\n- Implementing API requests\n- Setting up data fetching (React Query, SWR)\n- Using Expo Router data loaders (`useLoaderData`, web SDK 55+)\n- Debugging network failures\n- Implementing caching strategies\n- Handling offline scenarios\n- Authentication\u002Ftoken management\n- Configuring API URLs and environment variables\n\n## Preferences\n\n- Avoid axios, prefer expo\u002Ffetch\n\n## Common Issues & Solutions\n\n### 1. Basic Fetch Usage\n\n**Simple GET request**:\n\n```tsx\nconst fetchUser = async (userId: string) => {\n  const response = await fetch(`https:\u002F\u002Fapi.example.com\u002Fusers\u002F${userId}`);\n\n  if (!response.ok) {\n    throw new Error(`HTTP error! status: ${response.status}`);\n  }\n\n  return response.json();\n};\n```\n\n**POST request with body**:\n\n```tsx\nconst createUser = async (userData: UserData) => {\n  const response = await fetch(\"https:\u002F\u002Fapi.example.com\u002Fusers\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application\u002Fjson\",\n      Authorization: `Bearer ${token}`,\n    },\n    body: JSON.stringify(userData),\n  });\n\n  if (!response.ok) {\n    const error = await response.json();\n    throw new Error(error.message);\n  }\n\n  return response.json();\n};\n```\n\n---\n\n### 2. React Query (TanStack Query)\n\n**Setup**:\n\n```tsx\n\u002F\u002F app\u002F_layout.tsx\nimport { QueryClient, QueryClientProvider } from \"@tanstack\u002Freact-query\";\n\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      staleTime: 1000 * 60 * 5, \u002F\u002F 5 minutes\n      retry: 2,\n    },\n  },\n});\n\nexport default function RootLayout() {\n  return (\n    \u003CQueryClientProvider client={queryClient}>\n      \u003CStack \u002F>\n    \u003C\u002FQueryClientProvider>\n  );\n}\n```\n\n**Fetching data**:\n\n```tsx\nimport { useQuery } from \"@tanstack\u002Freact-query\";\n\nfunction UserProfile({ userId }: { userId: string }) {\n  const { data, isLoading, error, refetch } = useQuery({\n    queryKey: [\"user\", userId],\n    queryFn: () => fetchUser(userId),\n  });\n\n  if (isLoading) return \u003CLoading \u002F>;\n  if (error) return \u003CError message={error.message} \u002F>;\n\n  return \u003CProfile user={data} \u002F>;\n}\n```\n\n**Mutations**:\n\n```tsx\nimport { useMutation, useQueryClient } from \"@tanstack\u002Freact-query\";\n\nfunction CreateUserForm() {\n  const queryClient = useQueryClient();\n\n  const mutation = useMutation({\n    mutationFn: createUser,\n    onSuccess: () => {\n      \u002F\u002F Invalidate and refetch\n      queryClient.invalidateQueries({ queryKey: [\"users\"] });\n    },\n  });\n\n  const handleSubmit = (data: UserData) => {\n    mutation.mutate(data);\n  };\n\n  return \u003CForm onSubmit={handleSubmit} isLoading={mutation.isPending} \u002F>;\n}\n```\n\n---\n\n### 3. Error Handling\n\n**Comprehensive error handling**:\n\n```tsx\nclass ApiError extends Error {\n  constructor(message: string, public status: number, public code?: string) {\n    super(message);\n    this.name = \"ApiError\";\n  }\n}\n\nconst fetchWithErrorHandling = async (url: string, options?: RequestInit) => {\n  try {\n    const response = await fetch(url, options);\n\n    if (!response.ok) {\n      const error = await response.json().catch(() => ({}));\n      throw new ApiError(\n        error.message || \"Request failed\",\n        response.status,\n        error.code\n      );\n    }\n\n    return response.json();\n  } catch (error) {\n    if (error instanceof ApiError) {\n      throw error;\n    }\n    \u002F\u002F Network error (no internet, timeout, etc.)\n    throw new ApiError(\"Network error\", 0, \"NETWORK_ERROR\");\n  }\n};\n```\n\n**Retry logic**:\n\n```tsx\nconst fetchWithRetry = async (\n  url: string,\n  options?: RequestInit,\n  retries = 3\n) => {\n  for (let i = 0; i \u003C retries; i++) {\n    try {\n      return await fetchWithErrorHandling(url, options);\n    } catch (error) {\n      if (i === retries - 1) throw error;\n      \u002F\u002F Exponential backoff\n      await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000));\n    }\n  }\n};\n```\n\n---\n\n### 4. Authentication\n\n**Token management**:\n\n```tsx\nimport * as SecureStore from \"expo-secure-store\";\n\nconst TOKEN_KEY = \"auth_token\";\n\nexport const auth = {\n  getToken: () => SecureStore.getItemAsync(TOKEN_KEY),\n  setToken: (token: string) => SecureStore.setItemAsync(TOKEN_KEY, token),\n  removeToken: () => SecureStore.deleteItemAsync(TOKEN_KEY),\n};\n\n\u002F\u002F Authenticated fetch wrapper\nconst authFetch = async (url: string, options: RequestInit = {}) => {\n  const token = await auth.getToken();\n\n  return fetch(url, {\n    ...options,\n    headers: {\n      ...options.headers,\n      Authorization: token ? `Bearer ${token}` : \"\",\n    },\n  });\n};\n```\n\n**Token refresh**:\n\n```tsx\nlet isRefreshing = false;\nlet refreshPromise: Promise\u003Cstring> | null = null;\n\nconst getValidToken = async (): Promise\u003Cstring> => {\n  const token = await auth.getToken();\n\n  if (!token || isTokenExpired(token)) {\n    if (!isRefreshing) {\n      isRefreshing = true;\n      refreshPromise = refreshToken().finally(() => {\n        isRefreshing = false;\n        refreshPromise = null;\n      });\n    }\n    return refreshPromise!;\n  }\n\n  return token;\n};\n```\n\n---\n\n### 5. Offline Support\n\n**Check network status**:\n\n```tsx\nimport NetInfo from \"@react-native-community\u002Fnetinfo\";\n\n\u002F\u002F Hook for network status\nfunction useNetworkStatus() {\n  const [isOnline, setIsOnline] = useState(true);\n\n  useEffect(() => {\n    return NetInfo.addEventListener((state) => {\n      setIsOnline(state.isConnected ?? true);\n    });\n  }, []);\n\n  return isOnline;\n}\n```\n\n**Offline-first with React Query**:\n\n```tsx\nimport { onlineManager } from \"@tanstack\u002Freact-query\";\nimport NetInfo from \"@react-native-community\u002Fnetinfo\";\n\n\u002F\u002F Sync React Query with network status\nonlineManager.setEventListener((setOnline) => {\n  return NetInfo.addEventListener((state) => {\n    setOnline(state.isConnected ?? true);\n  });\n});\n\n\u002F\u002F Queries will pause when offline and resume when online\n```\n\n---\n\n### 6. Environment Variables\n\n**Using environment variables for API configuration**:\n\nExpo supports environment variables with the `EXPO_PUBLIC_` prefix. These are inlined at build time and available in your JavaScript code.\n\n```tsx\n\u002F\u002F .env\nEXPO_PUBLIC_API_URL=https:\u002F\u002Fapi.example.com\nEXPO_PUBLIC_API_VERSION=v1\n\n\u002F\u002F Usage in code\nconst API_URL = process.env.EXPO_PUBLIC_API_URL;\n\nconst fetchUsers = async () => {\n  const response = await fetch(`${API_URL}\u002Fusers`);\n  return response.json();\n};\n```\n\n**Environment-specific configuration**:\n\n```tsx\n\u002F\u002F .env.development\nEXPO_PUBLIC_API_URL=http:\u002F\u002Flocalhost:3000\n\n\u002F\u002F .env.production\nEXPO_PUBLIC_API_URL=https:\u002F\u002Fapi.production.com\n```\n\n**Creating an API client with environment config**:\n\n```tsx\n\u002F\u002F api\u002Fclient.ts\nconst BASE_URL = process.env.EXPO_PUBLIC_API_URL;\n\nif (!BASE_URL) {\n  throw new Error(\"EXPO_PUBLIC_API_URL is not defined\");\n}\n\nexport const apiClient = {\n  get: async \u003CT,>(path: string): Promise\u003CT> => {\n    const response = await fetch(`${BASE_URL}${path}`);\n    if (!response.ok) throw new Error(`HTTP ${response.status}`);\n    return response.json();\n  },\n\n  post: async \u003CT,>(path: string, body: unknown): Promise\u003CT> => {\n    const response = await fetch(`${BASE_URL}${path}`, {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application\u002Fjson\" },\n      body: JSON.stringify(body),\n    });\n    if (!response.ok) throw new Error(`HTTP ${response.status}`);\n    return response.json();\n  },\n};\n```\n\n**Important notes**:\n\n- Only variables prefixed with `EXPO_PUBLIC_` are exposed to the client bundle\n- Never put secrets (API keys with write access, database passwords) in `EXPO_PUBLIC_` variables—they're visible in the built app\n- Environment variables are inlined at **build time**, not runtime\n- Restart the dev server after changing `.env` files\n- For server-side secrets in API routes, use variables without the `EXPO_PUBLIC_` prefix\n\n**TypeScript support**:\n\n```tsx\n\u002F\u002F types\u002Fenv.d.ts\ndeclare global {\n  namespace NodeJS {\n    interface ProcessEnv {\n      EXPO_PUBLIC_API_URL: string;\n      EXPO_PUBLIC_API_VERSION?: string;\n    }\n  }\n}\n\nexport {};\n```\n\n---\n\n### 7. Request Cancellation\n\n**Cancel on unmount**:\n\n```tsx\nuseEffect(() => {\n  const controller = new AbortController();\n\n  fetch(url, { signal: controller.signal })\n    .then((response) => response.json())\n    .then(setData)\n    .catch((error) => {\n      if (error.name !== \"AbortError\") {\n        setError(error);\n      }\n    });\n\n  return () => controller.abort();\n}, [url]);\n```\n\n**With React Query** (automatic):\n\n```tsx\n\u002F\u002F React Query automatically cancels requests when queries are invalidated\n\u002F\u002F or components unmount\n```\n\n---\n\n## Decision Tree\n\n```\nUser asks about networking\n  |-- Route-level data loading (web, SDK 55+)?\n  |   \\-- Expo Router loaders — see references\u002Fexpo-router-loaders.md\n  |\n  |-- Basic fetch?\n  |   \\-- Use fetch API with error handling\n  |\n  |-- Need caching\u002Fstate management?\n  |   |-- Complex app -> React Query (TanStack Query)\n  |   \\-- Simpler needs -> SWR or custom hooks\n  |\n  |-- Authentication?\n  |   |-- Token storage -> expo-secure-store\n  |   \\-- Token refresh -> Implement refresh flow\n  |\n  |-- Error handling?\n  |   |-- Network errors -> Check connectivity first\n  |   |-- HTTP errors -> Parse response, throw typed errors\n  |   \\-- Retries -> Exponential backoff\n  |\n  |-- Offline support?\n  |   |-- Check status -> NetInfo\n  |   \\-- Queue requests -> React Query persistence\n  |\n  |-- Environment\u002FAPI config?\n  |   |-- Client-side URLs -> EXPO_PUBLIC_ prefix in .env\n  |   |-- Server secrets -> Non-prefixed env vars (API routes only)\n  |   \\-- Multiple environments -> .env.development, .env.production\n  |\n  \\-- Performance?\n      |-- Caching -> React Query with staleTime\n      |-- Deduplication -> React Query handles this\n      \\-- Cancellation -> AbortController or React Query\n```\n\n## Common Mistakes\n\n**Wrong: No error handling**\n\n```tsx\nconst data = await fetch(url).then((r) => r.json());\n```\n\n**Right: Check response status**\n\n```tsx\nconst response = await fetch(url);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst data = await response.json();\n```\n\n**Wrong: Storing tokens in AsyncStorage**\n\n```tsx\nawait AsyncStorage.setItem(\"token\", token); \u002F\u002F Not secure!\n```\n\n**Right: Use SecureStore for sensitive data**\n\n```tsx\nawait SecureStore.setItemAsync(\"token\", token);\n```\n\n## Example Invocations\n\nUser: \"How do I make API calls in React Native?\"\n-> Use fetch, wrap with error handling\n\nUser: \"Should I use React Query or SWR?\"\n-> React Query for complex apps, SWR for simpler needs\n\nUser: \"My app needs to work offline\"\n-> Use NetInfo for status, React Query persistence for caching\n\nUser: \"How do I handle authentication tokens?\"\n-> Store in expo-secure-store, implement refresh flow\n\nUser: \"API calls are slow\"\n-> Check caching strategy, use React Query staleTime\n\nUser: \"How do I configure different API URLs for dev and prod?\"\n-> Use EXPO*PUBLIC* env vars with .env.development and .env.production files\n\nUser: \"Where should I put my API key?\"\n-> Client-safe keys: EXPO*PUBLIC* in .env. Secret keys: non-prefixed env vars in API routes only\n\nUser: \"How do I load data for a page in Expo Router?\"\n-> See references\u002Fexpo-router-loaders.md for route-level loaders (web, SDK 55+). For native, use React Query or fetch.\n\n",{"name":5,"license":15,"version":54,"description":7},"1.0.0","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fexpo\u002Fskills\u002Fnative-data-fetching",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill native-data-fetching",{"tool":70,"command":71},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill native-data-fetching",{"tool":73,"command":74},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill native-data-fetching","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fexpo\u002Fskills\u002Fnative-data-fetching\u002FSKILL.md",true,[],[79,91,101,110,116,123],{"skillNo":80,"name":81,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":87,"publishedAt":49},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[84],{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[88,89,90],"model","model hierarchy","model hierarchy skill",{"skillNo":92,"name":93,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":97,"publishedAt":49},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[96],{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},[98,99,100],"MCP","zoom","zoom mcp",{"skillNo":102,"name":103,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":106,"publishedAt":49},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[107,108,109,99],"sdk","zoom apps sdk","apps",{"skillNo":111,"name":112,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":115,"publishedAt":49},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[112],{"skillNo":117,"name":118,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":119,"categories":120,"keywords":122,"publishedAt":49},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[121],{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},[118],{"skillNo":124,"name":125,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":126,"categories":127,"keywords":128,"publishedAt":49},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[125]]