Indice

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.login(body) Connexion utilisateur
api.logout() Déconnexion
api.public Instance pour requêtes publiques
api.private Instance pour requêtes authentifiées

Authentification

Login

snippet.javascript
import { useApi, useNavigation } from '@cap-rel/smartcommon';
 
function LoginPage() {
    const api = useApi();
    const navigate = useNavigation();
 
    const handleLogin = async (email, password) => {
        try {
            const user = await api.login({
                login: email,
                password: password,
                rememberMe: true
            });
 
            console.log('Connecté :', user);
            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 navigate = useNavigation();
 
    const handleLogout = async () => {
        await api.logout();
        navigate('/login');
    };
 
    return (
        <button onClick={handleLogout}>Déconnexion</button>
    );
}

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.private.delete(`items/${id}`);

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 navigate = 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.private.delete(`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={() => 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={() => 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' };
    }
};

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

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