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 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 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 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 pour plus de détails.

Astuce

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>
)}

Astuce

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 et Synchronisation offline.

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');

Astuce

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