native-data-fetching

Par expo · skills

À utiliser lors de l'implémentation ou du débogage de toute requête réseau, appel 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 Expo Router (`useLoaderData`).

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

Networking Expo

Vous DEVEZ utiliser cette skill pour TOUT travail en 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

Consultez ces ressources selon les besoins :

references/
  expo-router-loaders.md   Chargement de données au niveau des routes avec les loaders Expo Router (web, SDK 55+)

Quand l'utiliser

Utilisez 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

  • Éviter axios, préférer expo/fetch

Problèmes courants et solutions

1. Utilisation basique de Fetch

Requête GET simple :

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 récupérer à nouveau
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
  });

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

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

3. Gestion des erreurs

Gestion d'erreurs complète :

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 nouvelles tentatives :

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 pour 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

Vérifier le statut du réseau :

import NetInfo from "@react-native-community/netinfo";

// Hook pour le statut du réseau
function useNetworkStatus() {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    return NetInfo.addEventListener((state) => {
      setIsOnline(state.isConnected ?? true);
    });
  }, []);

  return isOnline;
}

Offline-first avec React Query :

import { onlineManager } from "@tanstack/react-query";
import NetInfo from "@react-native-community/netinfo";

// Synchroniser React Query avec le statut du réseau
onlineManager.setEventListener((setOnline) => {
  return NetInfo.addEventListener((state) => {
    setOnline(state.isConnected ?? true);
  });
});

// Les requêtes vont s'interrompre hors ligne et reprendre quand en ligne

6. Variables d'environnement

Utiliser les 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 de la construction et disponibles dans votre 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 par EXPO_PUBLIC_ sont exposées au bundle client
  • Ne jamais mettre de secrets (clés API avec accès en écriture, mots de passe base de données) dans les variables EXPO_PUBLIC_ — ils sont visibles dans l'app compilée
  • Les variables d'environnement sont inlinées au moment de la construction, pas à l'exécution
  • Redémarrer le serveur de dev après avoir changé les fichiers .env
  • Pour les secrets côté serveur dans les routes API, utiliser 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

Annuler au démontage :

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]);

Avec React Query (automatique) :

// React Query annule automatiquement les requêtes quand les requêtes sont invalidées
// ou que les composants se démontent

Arbre de décision

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

Erreurs courantes

Mauvais : Aucune gestion d'erreurs

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

Bon : 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();

Mauvais : Stocker les tokens dans AsyncStorage

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

Bon : 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, wrapper avec gestion d'erreurs

Utilisateur : "Dois-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, persistence React Query pour la mise en cache

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

Utilisateur : "Les appels API sont lents" -> Vérifier la stratégie de mise en cache, utiliser React Query staleTime

Utilisateur : "Comment configurer différentes URLs API pour dev et prod?" -> Utiliser les variables env EXPOPUBLIC avec les fichiers .env.development et .env.production

Utilisateur : "Où dois-je mettre ma clé API?" -> Clés sûres pour le client : EXPOPUBLIC dans .env. Clés secrètes : variables env sans préfixe dans les routes API seulement

Utilisateur : "Comment charger les 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 native, utiliser React Query ou fetch.

Skills similaires