[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FSQNZMBFVB7ZRV4PPZDT4G":3,"skill-same:01M6FSQNZMBFVB7ZRV4PPZDT4G":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},"01M6FSQNZMBFVB7ZRV4PPZDT4G","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).","screen-gd\u002FSyllo","screen-gd","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F228304862?v=4","01M6CY54TJFXPC22GBSJYYKSF1","Screen","01M6CY54YGY4QTM81699MG36A1",1,"MIT","https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002FCodex-Skills\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","# 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",{"name":5,"license":15,"version":54,"description":7},"1.0.0","main","8749e6a65f3d97164fb74ec18f758fc2d4fe1745",".agents\u002Fskills\u002FCodex-Skills\u002Fnative-data-fetching",0,"https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo","https:\u002F\u002Froadmap-nexus.vercel.app","https:\u002F\u002Fgithub.com\u002Fscreen-gd","Caffeine Addict game dev & Viber","2026-11-06 22:22:14",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":76},[66,69,72],{"tool":67,"command":68},"npx","npx skills add screen-gd\u002FSyllo --skill native-data-fetching",{"tool":70,"command":71},"bunx","bunx skills add screen-gd\u002FSyllo --skill native-data-fetching",{"tool":73,"command":74},"pnpm","pnpm dlx skills add screen-gd\u002FSyllo --skill native-data-fetching","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002FCodex-Skills\u002Fnative-data-fetching\u002FSKILL.md",true,[],[79,105,138,162,180,195],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":83,"categories":84,"keywords":86,"publishedAt":49},"01M6FVAXD6FCRNM1ZZAHGN7DGP","vercel-react-best-practices","React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React\u002FNext.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.","https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fvercel-react-best-practices\u002FSKILL.md",[85],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[48,87,88,89,90,91,92,93,94,95,96,97,98,31,99,100,101,102,103,104],"vercel react best practices","best practices","react best practices","vercel","writing","tasks","triggers","pages","next","performance optimization","refactoring","Next.js","Optimization","code","guidelines","react components","components","performance",{"skillNo":106,"name":107,"nameZh":6,"description":108,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":109,"categories":110,"keywords":120,"publishedAt":49},"01M6FTD7FAYNVH4BCYB54PDKER","scientific-writing","Core skill for the deep research and writing tool. Write scientific manuscripts in full paragraphs (never bullet points). Use two-stage process with (1) section outlines with key points using research-lookup then (2) convert to flowing prose. IMRAD...","https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002FCodex-Skills\u002Fscientific-writing\u002FSKILL.md",[111,114,117],{"categoryNo":112,"categoryName":113,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":115,"categoryName":116,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":118,"categoryName":119,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000036","内容创作者",[121,122,91,123,124,40,125,126,127,128,129,130,131,132,133,134,135,136,137],"scientific writing","scientific","prose","never","process","write","convert","tool","lookup","Outlines","deep research","core","research","deep","with","stage","full",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":147,"publishedAt":49},"01M6FT1TVECRNQ1GW27HNMNNM4","plotly","Interactive visualization library. Use when you need hover info, zoom, pan, or web-embeddable charts. Best for dashboards, exploratory analysis, and presentations. For static publication figures use matplotlib or scientific-visualization.","https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002FCodex-Skills\u002Fplotly\u002FSKILL.md",[144],{"categoryNo":145,"categoryName":146,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000013","分析与可视化",[140,148,149,150,151,152,153,40,154,155,156,157,158,159,160,122,161],"you","figures","zoom","matplotlib","hover","info","interactive","dashboards","Charts","analysis","library","visualization","presentations","web",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":169,"publishedAt":49},"01M6FRMCCT1EKPKZSXQ2M6TGX9","expo-tailwind-setup","Set up Tailwind CSS v4 in Expo with react-native-css and NativeWind v5 for universal styling","https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002FCodex-Skills\u002Fexpo-tailwind-setup\u002FSKILL.md",[168],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[170,171,172,38,173,48,135,174,29,175,176,177,178,179],"setup","expo tailwind setup","Tailwind","css","nativewind","Tailwind CSS","react-native","styling","up","SET",{"skillNo":181,"name":182,"nameZh":6,"description":183,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":184,"categories":185,"keywords":186,"publishedAt":49},"01M6FRMA1Y9K11J3FPFJHN5G4G","expo-dev-client","Build and distribute Expo development clients locally or via TestFlight","https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002FCodex-Skills\u002Fexpo-dev-client\u002FSKILL.md",[],[187,38,188,189,190,191,192,193,194],"client","expo dev client","dev","clients","via","distribute","build","development",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":199,"categories":200,"keywords":202,"publishedAt":49},"01M6FRM3M959CWA1ER4BMZCB0E","expo-cicd-workflows","Helps understand and write EAS workflow YAML files for Expo projects. Use this skill when the user asks about CI\u002FCD or workflows in an Expo or EAS context, mentions .eas\u002Fworkflows\u002F, or wants help with EAS build pipelines or deployment automation.","https:\u002F\u002Fgithub.com\u002Fscreen-gd\u002FSyllo\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002FCodex-Skills\u002Fexpo-cicd-workflows\u002FSKILL.md",[201],{"categoryNo":115,"categoryName":116,"taxonomyType":21,"taxonomyTypeDesc":22},[38,203,204,205,193,206,135,207,208,209,40,126,210,211,212,213,214,215,216,217],"cicd","expo cicd workflows","workflows","deployment automation","about","CI\u002FCD","files","read","projects","Bash","ci","help","deployment","automation","workflow"]