---
title: "Hooks SmartCommon"
weight: 130
---

# Hooks SmartCommon

SmartCommon fournit un ensemble de hooks React pour faciliter le développement d'applications.

## Hooks globaux

Ces hooks utilisent le contexte de l'application et doivent être utilisés à l'intérieur du `Provider`.

### useApi

Gestion des appels API avec authentification JWT automatique.

```
import { useApi } from '@cap-rel/smartcommon';

const api = useApi();

// Connexion
await api.login({ login, password, rememberMe: true });

// Déconnexion
await api.logout();

// Requête authentifiée
const data = await api.private.get('items').json();

// Requête publique
const info = await api.public.get('public/info').json();
```

Voir [Requêtes API](/front/requetes-api) pour plus de détails.

### useGlobalStates

État global avec persistance automatique (localStorage/sessionStorage).

```
import { useGlobalStates } from '@cap-rel/smartcommon';

const gst = useGlobalStates();

// Lire
const user = gst.get('user');
const theme = gst.get('settings.theme');

// Écrire (persistant)
gst.local.set('user', userData);

// Écrire (session uniquement)
gst.session.set('tempData', data);

// Supprimer
gst.unset('user');

// Accès direct aux valeurs
const { user, settings } = gst.values;
```

Voir [Stockage de données](/front/stockage-de-donnees) pour plus de détails.

### useNavigation

Utilitaires de navigation pour react-router-dom.

```
import { useNavigation } from '@cap-rel/smartcommon';

const nav = useNavigation();

// Navigation programmatique
nav.navigate('/dashboard');
nav.navigate(-1); // Retour arrière

// Informations de route
const { pathname, search, hash } = nav.location;

// Paramètres d'URL
const { id } = nav.params;
```

### useLibConfig

Accès à la configuration de l'application.

```
import { useLibConfig } from '@cap-rel/smartcommon';

const config = useLibConfig();

console.log(config.debug);     // true/false
console.log(config.api);       // { prefixUrl, timeout, ... }
```

### useConfirm

Dialogues de confirmation et d'alerte (nécessite ConfirmProvider).

```
import { useConfirm } from '@cap-rel/smartcommon';

const { confirm, alert } = useConfirm();

// Confirmation (retourne true/false)
const handleDelete = async () => {
  const ok = await confirm({
    type: 'delete',        // 'danger' | 'delete' | 'warning' | 'info'
    title: 'Supprimer ?',
    message: 'Cette action est irréversible.',
    detail: item.label,    // détail optionnel
    confirmText: 'Supprimer',
    cancelText: 'Annuler',
  });

  if (ok) {
    await api.del(`items/${item.id}`);
  }
};

// Alerte (un seul bouton OK)
await alert({
  type: 'info',
  title: 'Information',
  message: 'Opération terminée avec succès.',
});
```

### usePWAUpdate

Gestion des mises à jour PWA via Service Worker.

```
import { usePWAUpdate } from '@cap-rel/smartcommon';

const { updateAvailable, checkForUpdates, applyUpdate } = usePWAUpdate({
  autoReload: false,
  checkInterval: 60000,
});
```

Voir [PWA](/front/pwa) pour plus de détails.

## Hooks locaux

Ces hooks gèrent un état local au composant.

### useStates

Gestion d'état local avec path notation.

```
import { useStates } from '@cap-rel/smartcommon';

const st = useStates({
  initialStates: {
    count: 0,
    user: { name: '', email: '' },
    items: []
  },
  debug: true
});

// Lire
st.get('count');           // 0
st.get('user.name');       // ''
st.get('items[0]');        // undefined

// Écrire
st.set('count', 1);
st.set('user.name', 'John');
st.set('items[]', { id: 1 });  // Push dans le tableau

// Écrire avec fonction
st.set('count', prev => prev + 1);

// Supprimer
st.unset('user.email');
st.unset('items[0]');

// Accès direct
const { count, user, items } = st.values;
```

### useForm

Gestion de formulaires avec état et erreurs.

```
import { useForm } from '@cap-rel/smartcommon';

const form = useForm({
  defaultValues: {
    name: '',
    email: ''
  },
  debug: true
});

// Lire les valeurs
const name = form.get('values.name');

// Définir un champ avec validation
form.setField({
  name: 'email',
  value: 'test@example.com',
  errors: {
    required: { condition: !value },
    format: { condition: !isValidEmail(value) }
  }
});

// Vérifier les erreurs
const hasEmailError = form.get('errors.email.required');

// Accès direct
const { values, errors, isFormSubmitting, isFormSubmitted } = form;
```

### useDb

Base de données IndexedDB via Dexie avec logging automatique.

```
import { useDb } from '@cap-rel/smartcommon';

const db = useDb({
  name: 'myApp',
  version: 1,
  stores: {
    items: 'id++, name, category, createdAt',
    categories: 'id++, name'
  },
  debug: true
});

// CRUD via Dexie
await db.items.add({ name: 'Item 1' });
const all = await db.items.toArray();
await db.items.update(id, { name: 'Updated' });
await db.items.delete(id);
```

Ajoute automatiquement `createdAt`, `updatedAt` et une table `logs`.

Voir [Stockage de données](/front/stockage-de-donnees) pour plus de détails.

> [!TIP]
> Pour un cache API avec stratégies, préférez `useCachedQuery`. Pour la synchronisation offline, utilisez `useSyncClient`.

### useCachedQuery

Cache de requêtes avec stratégies réseau/cache. C'est le hook à utiliser pour une donnée **lue depuis l'API et relue souvent** : dictionnaires, configuration, listes de référence.

> [!IMPORTANT]
> Le nom exact est `useCachedQuery`, avec le `d`. `useCacheQuery` n'existe pas.

#### Déclarer le store de cache

Le hook stocke ses entrées dans un store Dexie que vous devez déclarer, indexé sur `key` :

```
// src/db/index.js
export const db = new Db({
  name: "myapp",
  version: 1,
  stores: {
    queryCache: 'key',
    // ... vos autres stores
  },
}).db;
```

#### Utilisation

```
import { useCachedQuery, CACHE_STRATEGIES } from '@cap-rel/smartcommon';
import { db } from 'src/db';

const {
  data,         // donnee, du reseau ou du cache
  isLoading,    // true pendant la recuperation
  isFromCache,  // true si la donnee vient du cache
  isStale,      // true si la donnee cachee est perimee
  error,        // erreur eventuelle
  lastFetch,    // timestamp de la derniere recuperation reussie
  refetch,      // relance la requete
  invalidate,   // vide le cache et relance
} = useCachedQuery({
  db: db.instance,             // instance Dexie (db.instance, pas db)
  store: 'queryCache',         // nom du store declare ci-dessus
  key: 'countries',            // cle de cache, unique par requete
  fetchFn: () => api.private.get('dictionaries/countries').json(),
  strategy: CACHE_STRATEGIES.CACHE_FIRST,
  ttl: 86400000,               // duree de vie du cache, defaut 1h
  staleTime: 60000,            // seuil de peremption, defaut 1min
  enabled: true,               // desactive la requete si false
});
```

#### Choisir la stratégie

| Stratégie | Comportement | Quand l'utiliser |
| --- | --- | --- |
| `NETWORK_FIRST` (`network-first`) | réseau d'abord, cache en secours si la requête échoue | défaut ; données métier qui doivent être fraîches mais doivent rester lisibles hors ligne |
| `CACHE_FIRST` (`cache-first`) | cache si encore valide, réseau sinon | dictionnaires et référentiels qui changent rarement (pays, unités, types) |
| `STALE_WHILE_REVALIDATE` (`swr`) | affiche le cache immédiatement, revalide en arrière-plan | configuration, préférences : l'affichage est instantané et se corrige tout seul |

#### ttl et staleTime, à ne pas confondre

| Option | Défaut | Effet |
| --- | --- | --- |
| `ttl` | 1 h | au-delà, l'entrée est **supprimée** et considérée comme absente |
| `staleTime` | 1 min | au-delà, l'entrée reste servie mais `isStale` passe à `true` |

`staleTime` est donc toujours inférieur à `ttl`. Un dictionnaire type se règle sur un `ttl` de 24 h.

#### Invalider

```
// Apres une ecriture qui rend le cache obsolete
await api.private.post('items', { json: payload });
await invalidate();     // vide l'entree et relance la requete
```

`refetch()` relance la requête sans vider le cache : la valeur cachée reste disponible si le réseau échoue.

#### Comportement hors ligne

Le hook s'appuie sur `useOnlineStatus`. Hors ligne, il sert le cache s'il existe et n'émet aucune requête. Testez `isFromCache` pour signaler à l'utilisateur qu'il consulte une donnée stockée localement.

```
{isFromCache && isStale && (
  <span className="text-xs italic">Données enregistrées le {formatDate(lastFetch)}</span>
)}
```

> [!TIP]
> `useCachedQuery` ne remplace pas la base métier. Une liste d'objets que l'utilisateur crée, modifie ou supprime hors ligne relève de `Db` et de `useSyncClient`. `useCachedQuery` est fait pour de la donnée **lue**, pas pour de la donnée produite.

Voir [Stockage de données](/front/stockage-de-donnees) et [Synchronisation offline](/front/synchronisation).

### useOnlineStatus

Détection de la connexion réseau avec health check optionnel.

```
import { useOnlineStatus } from '@cap-rel/smartcommon';

const { isOnline, isServerReachable, lastOnline, checkNow } = useOnlineStatus({
  healthCheckUrl: '/api/health',
  healthCheckInterval: 30000,
  stabilityDelay: 2000,
  timeout: 5000,
});
```

### useAuthenticatedImage

Chargement d'images authentifiées avec cache IndexedDB.

```
import { useAuthenticatedImage } from '@cap-rel/smartcommon';

const { src, isLoading, isFromCache, error } = useAuthenticatedImage({
  db: db.instance,              // instance Dexie
  url: `/api/users/${id}/photo`,
  token: accessToken,
  ttl: 86400000,                // durée de cache: 24h
  staleTime: 3600000,           // stale après 1h (refresh en arrière-plan)
  placeholder: '/images/default.png',
});

return <img src={src} alt="Photo" />;
```

## Hooks utilitaires

### useIntl

Formatage de dates et nombres avec l'API Intl.

```
import { useIntl } from '@cap-rel/smartcommon';

const intl = useIntl();

// Formater une date
const formatted = intl.DateTimeFormat(Date.now());
// "11/01/2025, 14:30:00"

// Avec options personnalisées
const dateOnly = intl.DateTimeFormat(Date.now(), 'fr-FR', {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
// "11 janvier 2025"
```

### useAnimation

Gestion d'animations avec Framer Motion.

```
import { useAnimation } from '@cap-rel/smartcommon';

const { start, animations, setAnimations } = useAnimation({
  fadeIn: { value: false, state: null },
  slideIn: { value: false, state: null }
});

// start devient true après le premier rendu
// Utilisez-le pour déclencher des animations d'entrée

useEffect(() => {
  if (start) {
    setAnimations(prev => ({
      ...prev,
      fadeIn: { ...prev.fadeIn, state: 'visible' }
    }));
  }
}, [start]);
```

### useFile

Utilitaires pour la gestion de fichiers.

```
import { useFile } from '@cap-rel/smartcommon';

const { resizeImage } = useFile();

// Redimensionner une image
const handleFileChange = async (e) => {
  const file = e.target.files[0];

  const base64 = await resizeImage(file, {
    maxWidth: 1920,
    maxHeight: 1080,
    quality: 85
  });

  console.log(base64); // data:image/jpeg;base64,...
};
```

### useVariantMerger

Fusion de props avec variants de composants (usage interne principalement).

```
import { useVariantMerger } from '@cap-rel/smartcommon';

const MyComponent = (props) => {
  const { variantProps, mergeProps, mergeQuickProps, setParams } = useVariantMerger('MyComponent', props);

  return (
    <div {...mergeProps('container', p => ({
      ...p,
      className: `base-class ${p.className || ''}`
    }))}>
      {variantProps.children}
    </div>
  );
};
```

| Propriété | Type | Description |
| --- | --- | --- |
| `variantProps` | object | Props fusionnées depuis le thème global + variant + props |
| `mergeProps` | function | `mergeProps(key, fn)` : fusionne les props d'un élément/sous-composant |
| `mergeQuickProps` | function | `mergeQuickProps(props, keys)` : extrait un sous-ensemble de variantProps |
| `setParams` | function | `setParams(params)` : définit des paramètres dynamiques pour les fonctions className |

### useListDnD

Drag and drop pour listes (réordonnancement). Prend en paramètre la fonction `set` de `useStates`.

```
import { useStates, useListDnD } from '@cap-rel/smartcommon';

const st = useStates({
  initialStates: {
    items: [{ id: 1, label: 'A' }, { id: 2, label: 'B' }],
    dragIndex: null,
  }
});

const { onDragStart, onDragOver, onDrop } = useListDnD(st.set);

// Utilisation sur chaque élément de liste
{st.get('items').map((item, index) => (
  <div
    key={item.id}
    draggable
    onDragStart={(e) => onDragStart(e, 'dragIndex', 'items')}
    onDragOver={(e) => onDragOver(e, 'dragIndex', index, 'items', st.get('items'))}
    onDrop={() => onDrop('dragIndex')}
  >
    {item.label}
  </div>
))}
```

Paramètres des callbacks :

| Callback | Paramètres | Description |
| --- | --- | --- |
| `onDragStart` | `(e, indexLabel, parent)` | `indexLabel` : clé d'état pour l'index glissé |
| `onDragOver` | `(e, indexLabel, index, listLabel, list)` | Réordonne la liste en temps réel |
| `onDrop` | `(indexLabel)` | Réinitialise l'index à null |

### useCalculator

Contrôle programmatique du composant `Calculator` (nécessite un `Calculator` dans l'arbre de composants).

```
import { useCalculator, Calculator } from '@cap-rel/smartcommon';

const MyComponent = () => {
  const { isOpen, open, close, toggle } = useCalculator();

  return (
    <>
      <button onClick={() => open((result) => console.log('Résultat:', result))}>
        Ouvrir la calculatrice
      </button>
      <Calculator />
    </>
  );
};
```

| Propriété | Type | Description |
| --- | --- | --- |
| `isOpen` | boolean | État d'ouverture de la calculatrice |
| `open` | function | `open(onResult?)` : ouvre la calculatrice avec callback optionnel |
| `close` | function | Ferme la calculatrice |
| `toggle` | function | Bascule l'ouverture/fermeture |

### useWindow

Informations sur la fenêtre du navigateur.

```
import { useWindow } from '@cap-rel/smartcommon';

const { orientation, windowDimension, scroll, darkMode } = useWindow();

// Dimensions
const { w, h } = windowDimension;

// Orientation : 'landscape' | 'portrait'
console.log(orientation);

// Position de scroll
const { x, y } = scroll;

// Mode sombre du système : true | false
console.log(darkMode);
```

### useIsDesktop

Détection responsive desktop/mobile.

```
import { useIsDesktop } from '@cap-rel/smartcommon';

const isDesktop = useIsDesktop();

if (isDesktop) {
  return <DesktopLayout />;
}
return <MobileLayout />;
```

### useStatesWorking

Gestion d'état local simplifiée avec paths imbriqués.

```
import { useStatesWorking } from '@cap-rel/smartcommon';

const { states, set, get, unset } = useStatesWorking({
  count: 0,
  user: { name: '', email: '' }
});

// Lire
get('user.name');

// Écrire (path notation)
set('user.name', 'John');
set('items[0]', { id: 1 });
set('items.[]', newItem);  // Push dans le tableau

// Supprimer
unset('user.email');
```

> [!TIP]
> Similaire à `useStates` mais avec une API plus directe : les valeurs initiales sont passées directement, sans wrapper `initialStates`.

### useSyncClient

Synchronisation offline/online avec gestion de conflits.

```
import { useSyncClient } from '@cap-rel/smartcommon';

const sync = useSyncClient({
  apiUrl: import.meta.env.VITE_API_URL,
  getAccessToken: () => api.accessToken,
  scope: ['items', 'categories']
});

// État de connexion
const { isOnline, isServerReachable } = sync;

// État de synchronisation
const { isInitialized, isSyncing, pendingCount, conflictsCount, lastSyncTime, syncError } = sync;

// Opérations CRUD locales (fonctionnent hors-ligne)
await sync.create('items', { ref: 'IT-001', label: 'Item 1' });
await sync.update('items', id, { label: 'Item modifié' });
await sync.remove('items', id);
await sync.upsert('items', { id, label: 'Créer ou modifier' });

// Lecture locale
const item = await sync.getEntity('items', id);
const items = await sync.queryEntities('items', { category: 'A' });

// Synchronisation manuelle
await sync.sync();      // Push + Pull
await sync.push();      // Envoyer les changements locaux
await sync.pull();      // Récupérer les changements serveur

// Gestion des conflits
const conflicts = await sync.getConflicts();
await sync.resolveConflict(conflictId, 'client'); // 'client' | 'server' | mergedData

// Enregistrement de l'appareil
await sync.register({ deviceName: 'Mon téléphone' });

// Réinitialisation
await sync.reset();
```

Voir [Synchronisation](/front/synchronisation) pour plus de détails.

## Tableau récapitulatif

| Hook | Catégorie | Description |
| --- | --- | --- |
| `useApi` | Global | Appels API avec auth JWT |
| `useGlobalStates` | Global | État global persistant |
| `useNavigation` | Global | Navigation react-router |
| `useLibConfig` | Global | Configuration de l'app |
| `useConfirm` | Global | Dialogues de confirmation |
| `usePWAUpdate` | Global | Mises à jour PWA |
| `useStates` | Local | État local avec path notation |
| `useStatesWorking` | Local | État local simplifié avec paths |
| `useForm` | Local | Gestion de formulaires |
| `useCachedQuery` | Local | Cache de données avec stratégies |
| `useOnlineStatus` | Local | Détection connexion réseau |
| `useAuthenticatedImage` | Local | Images authentifiées avec cache |
| `useSyncClient` | Sync | Synchronisation offline/online |
| `useIntl` | Utilitaire | Formatage dates/nombres |
| `useAnimation` | Utilitaire | Animations Framer Motion |
| `useFile` | Utilitaire | Manipulation de fichiers |
| `useVariantMerger` | Utilitaire | Fusion de variants |
| `useCalculator` | Utilitaire | Contrôle du composant Calculator |
| `useListDnD` | Utilitaire | Drag and drop |
| `useWindow` | Utilitaire | Infos fenêtre/orientation/scroll |
| `useIsDesktop` | Utilitaire | Détection desktop/mobile |

## Voir aussi
- [SmartCommon](/front/smartcommon) - Liste des composants
- [Requêtes API](/front/requetes-api) - Documentation détaillée useApi
- [Stockage de données](/front/stockage-de-donnees) - Documentation détaillée stockage
