• Projects
  • Service
  • About
  • branding.bz
  • Podcast
  • Tips
  • FAQ
  • Recruit
  • Download
  • Contact
  • branding.bz(ブランド構築SaaS)
  • DESIGN NOW(デザインメディア)
  • X
  • LinkedIn
  • Spotify
  • Facebook

213-0011 神奈川県川崎市高津区久本3-6-7-303

© 2026 ID INC. All rights reserved

claude-skills/スキル
SKILLOfficialdevelopment

expo-data-fetching

プラグイン
expo
ライセンス
MIT
ソース
GitHub で見る ↗
説明

フレームワーク(オープンソースソフトウェア)。ネットワークリクエスト、API呼び出し、またはデータ取得の実装やデバッグを行うあらゆる場面で使用します。 fetch API、React Query、SWR、エラーハンドリング(エラー処理)、キャッシング(データの一時保存)、オフラインサポート、および Expo Router のデータローダー(`useLoaderData`)に対応しています。

原文を表示

Framework (OSS). 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 ネットワーク機能

ネットワーク機能を使用するすべての作業(APIリクエスト、データ取得、キャッシング、ネットワーク デバッグなど)には、このスキルを必ず使用してください。

参考資料

必要に応じて以下を参照してください:

references/
  expo-router-loaders.md        Expo Routerのローダーを使用したルートレベルのデータ読み込み
                                (Web、SDK 55以上)
  offline-and-cancellation.md   NetInfoによるネットワーク状態確認、オフラインファーストの
                                React Query、AbortController

使用する場面

次のような場合に使用:

  • 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. オフラインサポート

NetInfoを使ったネットワーク状態検出とオフラインファーストのReact Query設定については、./references/offline-and-cancellation.md を参照してください。


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. リクエストのキャンセル

React Queryが自動的にキャンセルするため、マウント解除時にAbortControllerを使用してください。詳細は ./references/offline-and-cancellation.md を参照してください。


判断フロー

ネットワーク関連の質問か
  |-- ルートレベルのデータ読み込み(Web、SDK 55以上)?
  |   \-- Expo Routerのローダー → references/expo-router-loaders.md を参照
  |
  |-- 基本的なfetch?
  |   \-- エラーハンドリング付きのfetch APIを使用
  |
  |-- キャッシング・状態管理が必要?
  |   |-- 複雑なアプリ → React Query(TanStack Query)
  |   \-- シンプルな要件 → SWRまたはカスタムフック
  |
  |-- 認証が必要?
  |   |-- トークン保存 → 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 を設定

ユーザー: 「開発環境と本番環境で別のAPI URLを設定するには?」 → EXPO_PUBLIC_ プレフィックス付き環境変数で .env.development と .env.production ファイルを使用

ユーザー: 「APIキーはどこに置くべき?」 → クライアント可能なキー: .envで EXPO_PUBLIC_。シークレットキー: APIルート内のプレフィックスなし変数のみ

ユーザー: 「Expo Routerでページのデータを読み込むには?」 → ルートレベルのローダーについては references/expo-router-loaders.md を参照(Web、SDK 55以上)。ネイティブはReact Queryまたはfetchを使用

原文(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+)
  offline-and-cancellation.md   NetInfo network status, offline-first React Query, AbortController

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

Network-status detection with NetInfo and offline-first React Query setup: see ./references/offline-and-cancellation.md.


6. Environment Variables

Using environment variables for API configuration:

Expo supports environment variables with the EXPO_PUBLIC_ prefix. These are inlined at build time and available in your JavaScript code.

// .env
EXPO_PUBLIC_API_URL=https://api.example.com
EXPO_PUBLIC_API_VERSION=v1

// Usage in code
const API_URL = process.env.EXPO_PUBLIC_API_URL;

const fetchUsers = async () => {
  const response = await fetch(`${API_URL}/users`);
  return response.json();
};

Environment-specific configuration:

// .env.development
EXPO_PUBLIC_API_URL=http://localhost:3000

// .env.production
EXPO_PUBLIC_API_URL=https://api.production.com

Creating an API client with environment config:

// api/client.ts
const BASE_URL = process.env.EXPO_PUBLIC_API_URL;

if (!BASE_URL) {
  throw new Error("EXPO_PUBLIC_API_URL is not defined");
}

export const apiClient = {
  get: async <T,>(path: string): Promise<T> => {
    const response = await fetch(`${BASE_URL}${path}`);
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  },

  post: async <T,>(path: string, body: unknown): Promise<T> => {
    const response = await fetch(`${BASE_URL}${path}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  },
};

Important notes:

  • Only variables prefixed with EXPO_PUBLIC_ are exposed to the client bundle
  • Never put secrets (API keys with write access, database passwords) in EXPO_PUBLIC_ variables—they're visible in the built app
  • Environment variables are inlined at build time, not runtime
  • Restart the dev server after changing .env files
  • For server-side secrets in API routes, use variables without the EXPO_PUBLIC_ prefix

TypeScript support:

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

export {};

7. Request Cancellation

AbortController on unmount (React Query cancels automatically): see ./references/offline-and-cancellation.md.


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 EXPO_PUBLIC_ env vars with .env.development and .env.production files User: "Where should I put my API key?" -> Client-safe keys: EXPO_PUBLIC_ 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.

Submitting Feedback

If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:

npx --yes submit-expo-feedback@latest --category skills --subject "expo-data-fetching" "<actionable feedback>"

Only submit when you have something specific and actionable to report. Include as much relevant context as possible. If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.

原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。