---
title: "Chapitre 1 : useApi"
weight: 580
---

# 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` :

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

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

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

```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 :

```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 :

```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 :

```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 :

```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...) :

```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 :

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

## Exemple complet : CRUD

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

```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 :

```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 :

- Extrait le message d'erreur du body JSON (champ `error` ou `message`)
- Appelle `onApiError` avec ce message (sauf si `silent: true`)
- Détecte aussi les erreurs applicatives (HTTP 200 avec champ `error` dans 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

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

### Headers personnalisés

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

### Timeout personnalisé

```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](/training/module7-smartcommon-hooks) | [Chapitre suivant : Gestion d'état ->](/training/module7-smartcommon-hooks/etat)
