Table des matières

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 :

snippet.javascript
// 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

snippet.javascript
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

snippet.javascript
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

snippet.javascript
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 :

snippet.javascript
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 :

snippet.javascript
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 :

snippet.javascript
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 :

snippet.javascript
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…) :

snippet.javascript
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 :

snippet.javascript
const data = await api.get('optional-endpoint', { silent: true });

Exemple complet : CRUD

snippet.javascript
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

snippet.javascript
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 :

snippet.javascript
// 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 :

Fonctionnalités automatiques

Gestion des tokens JWT

Headers automatiques

Chaque requête inclut :

Circuit breaker

Protection contre les requêtes en cascade :

Paramètres de requête

Query string

snippet.javascript
// GET /products?category=electronics&limit=10
const products = await api.private.get('products', {
    searchParams: {
        category: 'electronics',
        limit: 10
    }
}).json();

Headers personnalisés

snippet.javascript
const data = await api.private.get('items', {
    headers: {
        'X-Custom-Header': 'value'
    }
}).json();

Timeout personnalisé

snippet.javascript
const data = await api.private.get('slow-endpoint', {
    timeout: 60000  // 60 secondes
}).json();

Points clés à retenir

  1. api.login() gère automatiquement le stockage des tokens
  2. api.private ajoute automatiquement l'authentification
  3. api.public pour les endpoints sans auth
  4. Terminer par .json() pour parser la réponse
  5. Le refresh token est géré automatiquement
  6. api.get/post/put/patch/del : raccourcis avec gestion d'erreur automatique
  7. api.user : accès direct à l'utilisateur connecté
  8. onApiError : centralise la notification des erreurs
  9. { raw: true } pour les fichiers binaires
  10. { silent: true } pour supprimer les notifications d'erreur

← Retour au module | Chapitre suivant : Gestion d'état →