🌐native-data-fetching
- プラグイン
- expo
- ライセンス
- MIT
- ソース
- GitHub で見る ↗
説明
次のような場合に使用: ネットワークリクエスト、APIコール、またはデータ取得の実装やデバッグを行う際。 fetch API、React Query、SWR、エラーハンドリング、キャッシング、オフラインサポート、およびExpo Routerのデータローダー(`useLoaderData`)に対応。
原文を表示
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`).
ユースケース
- ✓ネットワークリクエスト実装時
- ✓APIコール実装時
- ✓データ取得の実装時
- ✓データ取得のデバッグ時
本文(日本語訳)
Expo Networking
このskillは、APIリクエスト、データ取得、キャッシング、ネットワークデバッグを含む、ANY のネットワーク作業に使用する必要があります。
リファレンス
必要に応じて以下のリソースを参照してください:
references/
expo-router-loaders.md Expo Routerローダーを使用したルートレベルのデータ読み込み(web, SDK 55+)
使用時機
次のような場合に使用:
- APIリクエストの実装
- データ取得の設定(React Query、SWR)
- Expo Routerデータローダーの使用(
useLoaderData、web SDK 55+) - ネットワーク障害のデバッグ
- キャッシング戦略の実装
- オフラインシナリオへの対応
- 認証・トークン管理
- API URLと環境変数の設定
推奨事項
- axios は避け、expo/fetch を推奨
よくある問題と解決方法
1. 基本的な Fetch の使用法
シンプルな GET リクエスト:
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 リクエスト:
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)
セットアップ:
// app/_layout.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5分
retry: 2,
},
},
});
export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
<Stack />
</QueryClientProvider>
);
}
データ取得:
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: () => {
// キャッシュを無効化して再取得
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
const handleSubmit = (data: UserData) => {
mutation.mutate(data);
};
return <Form onSubmit={handleSubmit} isLoading={mutation.isPending} />;
}
3. エラーハンドリング
包括的なエラーハンドリング:
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;
}
// ネットワークエラー(インターネット接続なし、タイムアウトなど)
throw new ApiError("Network error", 0, "NETWORK_ERROR");
}
};
リトライロジック:
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;
// 指数バックオフ
await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000));
}
}
};
4. 認証
トークン管理:
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),
};
// 認証付き fetch ラッパー
const authFetch = async (url: string, options: RequestInit = {}) => {
const token = await auth.getToken();
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: token ? `Bearer ${token}` : "",
},
});
};
トークンの更新:
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. オフラインサポート
ネットワーク状態の確認:
import NetInfo from "@react-native-community/netinfo";
// ネットワーク状態用 Hook
function useNetworkStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
return NetInfo.addEventListener((state) => {
setIsOnline(state.isConnected ?? true);
});
}, []);
return isOnline;
}
React Query によるオフラインファースト対応:
import { onlineManager } from "@tanstack/react-query";
import NetInfo from "@react-native-community/netinfo";
// React Query をネットワーク状態と同期
onlineManager.setEventListener((setOnline) => {
return NetInfo.addEventListener((state) => {
setOnline(state.isConnected ?? true);
});
});
// オフライン時はクエリが一時停止し、オンライン時に再開
6. 環境変数
API 設定用の環境変数の使用:
Expo は EXPO_PUBLIC_ プレフィックス付きの環境変数をサポートしています。これらはビルド時にインライン化され、JavaScript コードで利用可能です。
// .env
EXPO_PUBLIC_API_URL=https://api.example.com
EXPO_PUBLIC_API_VERSION=v1
// コード内での使用
const API_URL = process.env.EXPO_PUBLIC_API_URL;
const fetchUsers = async () => {
const response = await fetch(`${API_URL}/users`);
return response.json();
};
環境別の設定:
// .env.development
EXPO_PUBLIC_API_URL=http://localhost:3000
// .env.production
EXPO_PUBLIC_API_URL=https://api.production.com
環境設定を使用した API クライアントの作成:
// 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();
},
};
重要な注記:
EXPO_PUBLIC_プレフィックス付きの変数のみがクライアントバンドルに露出されます- シークレット(書き込みアクセス権限のある API キー、データベースパスワードなど)を
EXPO_PUBLIC_変数に入れないでください。ビルド済みアプリに表示されます - 環境変数はビルド時にインライン化されます。ランタイムではありません
.envファイルを変更した後は開発サーバーを再起動してください- API ルート内のサーバーサイドシークレットについては、
EXPO_PUBLIC_プレフィックスなしの変数を使用してください
TypeScript サポート:
// types/env.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
EXPO_PUBLIC_API_URL: string;
EXPO_PUBLIC_API_VERSION?: string;
}
}
}
export {};
7. リクエストキャンセル
アンマウント時にキャンセル:
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]);
React Query の場合(自動):
// React Query は、クエリが無効化されたとき、
// またはコンポーネントがアンマウントされたときに
// リクエストを自動的にキャンセルします
判定木
ユーザーがネットワークについて質問
|-- ルートレベルのデータ読み込み(web、SDK 55+)?
| \-- Expo Router ローダー — references/expo-router-loaders.md を参照
|
|-- 基本的な fetch?
| \-- エラーハンドリング付きで fetch API を使用
|
|-- キャッシング・状態管理が必要?
| |-- 複雑なアプリ -> React Query (TanStack Query)
| \-- よりシンプルな用途 -> SWR またはカスタム Hook
|
|-- 認証?
| |-- トークン保存 -> expo-secure-store
| \-- トークン更新 -> リフレッシュフローを実装
|
|-- エラーハンドリング?
| |-- ネットワークエラー -> 最初に接続性を確認
| |-- HTTP エラー -> レスポンスをパースして型付きエラーをスロー
| \-- リトライ -> 指数バックオフ
|
|-- オフラインサポート?
| |-- 状態確認 -> NetInfo
| \-- リクエストキューイング -> React Query の永続化
|
|-- 環境・API 設定?
| |-- クライアント側 URL -> .env の EXPO_PUBLIC_ プレフィックス
| |-- サーバーシークレット -> プレフィックスなしの環境変数(API ルートのみ)
| \-- 複数環境 -> .env.development、.env.production
|
\-- パフォーマンス?
|-- キャッシング -> React Query と staleTime
|-- 重複排除 -> React Query が処理
\-- キャンセル -> AbortController または React Query
よくある間違い
悪い例: エラーハンドリングなし
const data = await fetch(url).then((r) => r.json());
良い例: レスポンスステータスを確認
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
悪い例: AsyncStorage にトークンを保存
await AsyncStorage.setItem("token", token); // セキュアではありません!
良い例: 機密データには SecureStore を使用
await SecureStore.setItemAsync("token", token);
使用例
ユーザー: 「React Native で API 呼び出しをどうやるのですか?」 → fetch を使用し、エラーハンドリングでラップしてください
ユーザー: 「React Query と SWR のどちらを使うべきですか?」 → 複雑なアプリは React Query、よりシンプルなニーズは SWR
ユーザー: 「アプリがオフラインで動作する必要があります」 → ステータス確認に NetInfo を、キャッシングに React Query の永続化を使用
ユーザー: 「認証トークンをどうやって処理するのですか?」 → expo-secure-store に保存し、リフレッシュフローを実装
ユーザー: 「API 呼び出しが遅いです」 → キャッシング戦略を確認し、React Query の staleTime を使用
ユーザー: 「開発環境
原文(English)を表示
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
.envfiles - 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.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。