expo-data-fetching

Par expo · skills

Framework (OSS). À utiliser lors de l'implémentation ou du débogage de toute requête réseau, appel d'API ou récupération de données. Couvre l'API fetch, React Query, SWR, la gestion des erreurs, le cache, le support hors ligne et les data loaders d'Expo Router (`useLoaderData`).

npx skills add https://github.com/expo/skills --skill expo-data-fetching

Expo Networking

Tu DOIS utiliser cette skill pour TOUT travail réseau incluant les requêtes API, la récupération de données, la mise en cache ou le débogage réseau.

Références

Consulte ces ressources au besoin :

references/
  expo-router-loaders.md        Chargement de données au niveau des routes avec les loaders Expo Router (web, SDK 55+)
  offline-and-cancellation.md   Statut réseau NetInfo, React Query offline-first, AbortController

Quand utiliser

Utilise cette skill quand :

  • Implémenter des requêtes API
  • Configurer la récupération de données (React Query, SWR)
  • Utiliser les loaders de données Expo Router (useLoaderData, web SDK 55+)
  • Déboguer les défaillances réseau
  • Implémenter des stratégies de mise en cache
  • Gérer les scénarios hors ligne
  • Gestion de l'authentification/tokens
  • Configurer les URLs API et les variables d'environnement

Préférences

  • Évite axios, préfère expo/fetch

Problèmes courants et solutions

1. Utilisation basique de Fetch

Simple requête 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();
};

Requête POST avec 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)

Configuration :

// 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>
  );
}

Récupération de données :

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: () => {
      // Invalider et rafraîchir
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
  });

  const handleSubmit = (data: UserData) => {
    mutation.mutate(data);
  };

  return <Form onSubmit={handleSubmit} isLoading={mutation.isPending} />;
}

3. Gestion des erreurs

Gestion complète des erreurs :

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;
    }
    // Erreur réseau (pas d'internet, timeout, etc.)
    throw new ApiError("Network error", 0, "NETWORK_ERROR");
  }
};

Logique de retry :

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;
      // Backoff exponentiel
      await new Promise((r) => setTimeout(r, Math.pow(2, i) * 1000));
    }
  }
};

4. Authentification

Gestion des tokens :

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),
};

// Wrapper fetch authentifié
const authFetch = async (url: string, options: RequestInit = {}) => {
  const token = await auth.getToken();

  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      Authorization: token ? `Bearer ${token}` : "",
    },
  });
};

Rafraîchissement du 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. Support hors ligne

Détection du statut réseau avec NetInfo et configuration React Query offline-first : voir ./references/offline-and-cancellation.md.


6. Variables d'environnement

Utiliser des variables d'environnement pour la configuration API :

Expo supporte les variables d'environnement avec le préfixe EXPO_PUBLIC_. Elles sont inlinées au moment du build et disponibles dans ton code JavaScript.

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

// Utilisation dans le code
const API_URL = process.env.EXPO_PUBLIC_API_URL;

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

Configuration spécifique à l'environnement :

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

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

Créer un client API avec configuration d'environnement :

// 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();
  },
};

Notes importantes :

  • Seules les variables préfixées avec EXPO_PUBLIC_ sont exposées dans le bundle client
  • Ne mets jamais de secrets (clés API avec accès en écriture, mots de passe de base de données) dans des variables EXPO_PUBLIC_ — elles sont visibles dans l'app compilée
  • Les variables d'environnement sont inlinées au moment du build, pas à l'exécution
  • Redémarre le serveur de développement après avoir modifié les fichiers .env
  • Pour les secrets côté serveur dans les routes API, utilise des variables sans le préfixe EXPO_PUBLIC_

Support TypeScript :

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

export {};

7. Annulation de requête

AbortController au démontage (React Query annule automatiquement) : voir ./references/offline-and-cancellation.md.


Arbre de décision

L'utilisateur pose une question sur le networking
  |-- Chargement de données au niveau des routes (web, SDK 55+)?
  |   \-- Loaders Expo Router — voir references/expo-router-loaders.md
  |
  |-- Fetch basique?
  |   \-- Utiliser l'API fetch avec gestion d'erreurs
  |
  |-- Besoin de caching/gestion d'état?
  |   |-- App complexe -> React Query (TanStack Query)
  |   \-- Besoins plus simples -> SWR ou hooks personnalisés
  |
  |-- Authentification?
  |   |-- Stockage de token -> expo-secure-store
  |   \-- Rafraîchissement de token -> Implémenter un flux de rafraîchissement
  |
  |-- Gestion des erreurs?
  |   |-- Erreurs réseau -> Vérifier la connectivité d'abord
  |   |-- Erreurs HTTP -> Analyser la réponse, lancer des erreurs typées
  |   \-- Retries -> Backoff exponentiel
  |
  |-- Support hors ligne?
  |   |-- Vérifier le statut -> NetInfo
  |   \-- Mettre en file d'attente les requêtes -> Persistance React Query
  |
  |-- Configuration d'environnement/API?
  |   |-- URLs côté client -> Préfixe EXPO_PUBLIC_ dans .env
  |   |-- Secrets serveur -> Variables env sans préfixe (API routes seulement)
  |   \-- Environnements multiples -> .env.development, .env.production
  |
  \-- Performance?
      |-- Caching -> React Query avec staleTime
      |-- Déduplication -> React Query s'en charge
      \-- Annulation -> AbortController ou React Query

Erreurs courantes

Faux : Pas de gestion d'erreurs

const data = await fetch(url).then((r) => r.json());

Correct : Vérifier le statut de la réponse

const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();

Faux : Stocker les tokens dans AsyncStorage

await AsyncStorage.setItem("token", token); // Pas sécurisé!

Correct : Utiliser SecureStore pour les données sensibles

await SecureStore.setItemAsync("token", token);

Exemples d'invocations

Utilisateur : "Comment faire des appels API en React Native?" -> Utiliser fetch, envelopper avec gestion d'erreurs

Utilisateur : "Devrais-je utiliser React Query ou SWR?" -> React Query pour les apps complexes, SWR pour les besoins plus simples

Utilisateur : "Mon app doit fonctionner hors ligne" -> Utiliser NetInfo pour le statut, persistance React Query pour la mise en cache

Utilisateur : "Comment gérer les tokens d'authentification?" -> Stocker dans expo-secure-store, implémenter un flux de rafraîchissement

Utilisateur : "Les appels API sont lents" -> Vérifier la stratégie de caching, utiliser React Query staleTime Utilisateur : "Comment configurer différentes URLs API pour dev et prod?" -> Utiliser des variables env EXPO_PUBLIC_ avec les fichiers .env.development et .env.production Utilisateur : "Où mettre ma clé API?" -> Clés sûres côté client : EXPO_PUBLIC_ dans .env. Clés secrètes : variables env sans préfixe dans les routes API seulement

Utilisateur : "Comment charger des données pour une page dans Expo Router?" -> Voir references/expo-router-loaders.md pour les loaders au niveau des routes (web, SDK 55+). Pour le natif, utiliser React Query ou fetch.

Skills similaires