[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-dc260d47-db00-4770-ad82-a48b44b95baf":3,"$fEv07Ek39iMc_kqmFO9HduWBlmhyPECX-Y0OAUPerTTs":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"dc260d47-db00-4770-ad82-a48b44b95baf","native-data-fetching","用于实现或调试任何网络请求、API调用或数据获取。涵盖fetch API、React Query、SWR、错误处理、缓存、离线支持以及Expo Router数据加载器（使用useLoaderData）。","cat_coding_frontend","mod_coding","sickn33,coding","---\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).\nrisk: unknown\nsource: community\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\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## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,236,172,"2026-05-16 13:30:30",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"前端开发","frontend","mdi-language-html5","HTML\u002FCSS\u002FJavaScript\u002F框架相关",1,96,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"018f41b4-2391-4a31-9651-50a08b8078fe","1.0.0","native-data-fetching.zip",4344,"uploads\u002Fskills\u002Fdc260d47-db00-4770-ad82-a48b44b95baf\u002Fnative-data-fetching.zip","43a96377a7cc40eb549c3d22c17cf6b30845c7a82c5b67e79e248691e7dd0954","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":11993}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]