Chapitre 1 : useApi
Le hook useApi simplifie les appels API avec gestion automatique de l'authentification JWT, du rafraîchissement de token et de la gestion des erreurs.
Configuration
L'API est configurée dans appConfig.js :
// src/appConfig.js
export const config = {
api: {
prefixUrl: import.meta.env.VITE_API_URL,
timeout: 30000,
debug: import.meta.env.DEV,
paths: {
login: "login",
logout: "logout",
refresh: "refresh"
}
}
};
Structure retournée
import { useApi } from '@cap-rel/smartcommon';
const api = useApi();
| Méthode | Description |
|---|---|
| api.user | Objet utilisateur connecté |
| api.login(body) | Connexion utilisateur |
| api.logout() | Déconnexion |
| api.entities() | Récupérer les entités disponibles |
| api.device(body) | Enregistrer un appareil |
| api.public | Instance ky pour requêtes publiques |
| api.private | Instance ky pour requêtes authentifiées |
| api.get(url, options) | Raccourci GET authentifié |
| api.post(url, options) | Raccourci POST authentifié |
| api.put(url, options) | Raccourci PUT authentifié |
| api.patch(url, options) | Raccourci PATCH authentifié |
| api.del(url, options) | Raccourci DELETE authentifié |
Authentification
Login
import { useApi, useNavigation } from '@cap-rel/smartcommon';
function LoginPage() {
const api = useApi();
const nav = useNavigation();
const handleLogin = async (email, password) => {
try {
const user = await api.login({
login: email,
password: password,
rememberMe: true
});
console.log('Connecté :', user);
nav.navigate('/');
} catch (error) {
console.error('Erreur de connexion:', error);
alert('Identifiants incorrects');
}
};
return (
<form onSubmit={(e) => {
e.preventDefault();
const fd = new FormData(e.target);
handleLogin(fd.get('email'), fd.get('password'));
}}>
<input name="email" type="email" placeholder="Email" />
<input name="password" type="password" placeholder="Mot de passe" />
<button type="submit">Connexion</button>
</form>
);
}
Logout
function LogoutButton() {
const api = useApi();
const nav = useNavigation();
const handleLogout = async () => {
await api.logout();
nav.navigate('/login');
};
return (
<button onClick={handleLogout}>Déconnexion</button>
);
}
Accès à l'utilisateur connecté
useApi expose directement l'objet utilisateur :
const api = useApi();
if (api.user) {
console.log('Connecté :', api.user.login);
console.log('Token expire à :', api.user.tokenExpiry);
}
Requêtes publiques
Pour les endpoints sans authentification :
const api = useApi();
// GET
const info = await api.public.get('public/info').json();
// POST
const result = await api.public.post('public/contact', {
json: { name: 'Jean', message: 'Bonjour' }
}).json();
Requêtes authentifiées
Pour les endpoints protégés par JWT :
const api = useApi();
// GET - Liste
const items = await api.private.get('items').json();
// GET - Détail
const item = await api.private.get(`items/${id}`).json();
// POST - Création
const newItem = await api.private.post('items', {
json: { label: 'Nouveau produit', price: 29.99 }
}).json();
// PUT - Modification
const updated = await api.private.put(`items/${id}`, {
json: { label: 'Produit modifié' }
}).json();
// DELETE - Suppression
await api.del(`items/${id}`);
Méthodes raccourcies
En plus des méthodes api.private.get(...).json(), useApi expose des raccourcis qui gèrent automatiquement le parsing JSON et les erreurs :
const api = useApi();
// GET - retourne directement les données parsées
const items = await api.get('items');
// POST
const created = await api.post('items', { json: { label: 'Nouveau' } });
// PUT
const updated = await api.put(`items/${id}`, { json: data });
// PATCH
const patched = await api.patch(`items/${id}`, { json: { status: 1 } });
// DELETE
await api.del(`items/${id}`);
Option raw
Pour récupérer des données binaires (images, fichiers Excel, PDF...) :
const response = await api.get(`temp-file/${token}/binary`, { raw: true });
const blob = await response.blob();
Option silent
Supprime la notification automatique en cas d'erreur :
const data = await api.get('optional-endpoint', { silent: true });
Exemple complet : CRUD
import { useEffect } from 'react';
import { Page, Block, List, ListItem, Button, Spinner } from '@cap-rel/smartcommon';
import { useApi, useStates, useNavigation } from '@cap-rel/smartcommon';
export const ProductsPage = () => {
const api = useApi();
const nav = useNavigation();
const st = useStates({
initialStates: {
products: [],
loading: true,
error: null
}
});
// Charger les produits
useEffect(() => {
loadProducts();
}, []);
const loadProducts = async () => {
st.set('loading', true);
st.set('error', null);
try {
const data = await api.private.get('products').json();
st.set('products', data.products);
} catch (err) {
st.set('error', err.message);
} finally {
st.set('loading', false);
}
};
// Supprimer un produit
const deleteProduct = async (id) => {
if (!confirm('Supprimer ce produit ?')) return;
try {
await api.del(`products/${id}`);
// Retirer de la liste locale
st.set('products', st.get('products').filter(p => p.id !== id));
} catch (err) {
alert('Erreur : ' + err.message);
}
};
if (st.get('loading')) {
return <Page title="Produits"><Spinner /></Page>;
}
if (st.get('error')) {
return (
<Page title="Produits">
<Block>
<p>Erreur : {st.get('error')}</p>
<Button onClick={loadProducts}>Réessayer</Button>
</Block>
</Page>
);
}
return (
<Page title="Produits" onRefresh={loadProducts}>
<Block>
<Button onClick={() => nav.navigate('/products/new')}>
Nouveau produit
</Button>
</Block>
<Block>
<List>
{st.get('products').map(product => (
<ListItem
key={product.id}
title={product.label}
subtitle={`${product.price} €`}
onClick={() => nav.navigate(`/products/${product.id}`)}
actions={
<Button
size="sm"
variant="danger"
onClick={(e) => {
e.stopPropagation();
deleteProduct(product.id);
}}
>
Supprimer
</Button>
}
/>
))}
</List>
</Block>
</Page>
);
};
Gestion des erreurs
const api = useApi();
const createProduct = async (data) => {
try {
const result = await api.private.post('products', { json: data }).json();
return { success: true, data: result };
} catch (error) {
// Erreur HTTP
if (error.response) {
const status = error.response.status;
const body = await error.response.json().catch(() => ({}));
if (status === 400) {
return { success: false, error: 'Données invalides', details: body };
}
if (status === 401) {
return { success: false, error: 'Session expirée' };
}
if (status === 403) {
return { success: false, error: 'Accès interdit' };
}
if (status === 404) {
return { success: false, error: 'Ressource non trouvée' };
}
if (status >= 500) {
return { success: false, error: 'Erreur serveur' };
}
}
// Erreur réseau
return { success: false, error: 'Erreur de connexion' };
}
};
Gestion globale des erreurs (onApiError)
Configurez un callback global dans appConfig pour centraliser le traitement des erreurs API :
// src/appConfig.js
import toast from 'react-hot-toast';
export const config = {
api: {
prefixUrl: import.meta.env.VITE_API_URL,
onApiError: (message) => {
toast.error(message);
},
},
};
Quand une requête échoue via les méthodes raccourcies (get, post, put, patch, del), useApi :
- Extrait le message d'erreur du body JSON (champ
erroroumessage) - Appelle
onApiErroravec ce message (sauf sisilent: true) - Détecte aussi les erreurs applicatives (HTTP 200 avec champ
errordans le body)
Fonctionnalités automatiques
Gestion des tokens JWT
- Le token d'accès est ajouté automatiquement aux requêtes
api.private - Le token est rafraîchi automatiquement avant expiration
- En cas d'erreur 401, un refresh est tenté automatiquement
Headers automatiques
Chaque requête inclut :
Authorization: Bearer <accessToken>(requêtes private)X-DEVICEID: <uuid>(identification de l'appareil)Content-Type: application/json
Circuit breaker
Protection contre les requêtes en cascade :
- Blocage temporaire après plusieurs échecs
- Détection automatique de la connexion internet
Paramètres de requête
Query string
// GET /products?category=electronics&limit=10
const products = await api.private.get('products', {
searchParams: {
category: 'electronics',
limit: 10
}
}).json();
Headers personnalisés
const data = await api.private.get('items', {
headers: {
'X-Custom-Header': 'value'
}
}).json();
Timeout personnalisé
const data = await api.private.get('slow-endpoint', {
timeout: 60000 // 60 secondes
}).json();
Points clés à retenir
- api.login() gère automatiquement le stockage des tokens
- api.private ajoute automatiquement l'authentification
- api.public pour les endpoints sans auth
- Terminer par .json() pour parser la réponse
- Le refresh token est géré automatiquement
- api.get/post/put/patch/del : raccourcis avec gestion d'erreur automatique
- api.user : accès direct à l'utilisateur connecté
- onApiError : centralise la notification des erreurs
- { raw: true } pour les fichiers binaires
- { silent: true } pour supprimer les notifications d'erreur