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.