---
title: "Stockage de données"
weight: 180
---

# Stockage de données

SmartCommon propose plusieurs solutions pour le stockage de données côté client :
- `useGlobalStates` : état global avec persistance automatique (localStorage/sessionStorage)
- `useStates` : état local réactif avec path notation
- `useDb` : base de données IndexedDB via Dexie
- `useCachedQuery` : cache de données avec stratégies (NetworkFirst, CacheFirst, SWR)

Pour la synchronisation offline complète, voir [Synchronisation offline](/front/synchronisation) (`useSyncClient`).

Documentation [IndexedDB](https://developer.mozilla.org/fr/docs/Web/API/IndexedDB_API)
Documentation [Dexie](https://dexie.org/)
Documentation [Redux Toolkit](https://redux-toolkit.js.org/)

## useGlobalStates (recommandé)

Le hook `useGlobalStates` fournit un état global avec persistance automatique. C'est la méthode recommandée pour les données utilisateur (session, préférences, etc.).

### Utilisation basique

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

export const MyComponent = () => {
  const gst = useGlobalStates();

  // Lire une valeur
  const user = gst.get('user');

  // Écrire dans localStorage (persistant)
  const login = (userData) => {
    gst.local.set('user', userData);
  };

  // Écrire dans sessionStorage (session uniquement)
  const setTempData = (data) => {
    gst.session.set('tempData', data);
  };

  // Supprimer une valeur
  const logout = () => {
    gst.unset('user');
  };

  return (
    <div>
      {user ? `Bonjour ${user.name}` : 'Non connecté'}
    </div>
  );
};
```

### Méthodes disponibles

| Méthode | Description |
| --- | --- |
| `gst.get(path)` | Récupère une valeur par son chemin |
| `gst.local.set(path, value)` | Stocke dans localStorage (persistant) |
| `gst.session.set(path, value)` | Stocke dans sessionStorage (session) |
| `gst.unset(path)` | Supprime une valeur |
| `gst.values` | Objet contenant toutes les valeurs |

### Path notation

Vous pouvez accéder aux données imbriquées avec la notation par points :

```
// Définir des données imbriquées
gst.local.set('user.preferences.theme', 'dark');
gst.local.set('user.preferences.language', 'fr');

// Lire des données imbriquées
const theme = gst.get('user.preferences.theme'); // 'dark'
const prefs = gst.get('user.preferences'); // { theme: 'dark', language: 'fr' }
```

## useStates (état local)

Le hook `useStates` fournit un état local réactif avec la même API que `useGlobalStates`.

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

export const MyForm = () => {
  const st = useStates({
    initialStates: {
      name: '',
      email: '',
      errors: {}
    },
    debug: true // affiche les changements dans la console
  });

  const handleChange = (field, value) => {
    st.set(field, value);
  };

  const validate = () => {
    if (!st.get('name')) {
      st.set('errors.name', 'Le nom est requis');
    }
  };

  return (
    <form>
      <Input
        value={st.get('name')}
        onChange={(e) => handleChange('name', e.target.value)}
        error={st.get('errors.name')}
      />
    </form>
  );
};
```

### Méthodes disponibles

| Méthode | Description |
| --- | --- |
| `st.get(path)` | Récupère une valeur |
| `st.set(path, value)` | Définit une valeur |
| `st.unset(path)` | Supprime une valeur |
| `st.values` | Objet contenant toutes les valeurs |
| `st.states` | Alias de values |

### Manipulation de tableaux

```
const st = useStates({ initialStates: { items: [] } });

// Ajouter un élément (push)
st.set('items[]', { id: 1, name: 'Item 1' });

// Modifier un élément par index
st.set('items[0].name', 'Item modifié');

// Supprimer un élément par index
st.unset('items[0]');
```

## useCachedQuery (cache avec stratégies)

Le hook `useCachedQuery` permet de mettre en cache les données API dans IndexedDB avec des stratégies configurables.

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

const ItemsList = () => {
  const db = useMemo(() => new Dexie('myApp'), []);

  const {
    data,          // données (depuis cache ou réseau)
    isLoading,     // chargement en cours
    isFromCache,   // true si les données viennent du cache
    isStale,       // true si les données sont périmées
    error,         // erreur éventuelle
    lastFetch,     // timestamp du dernier fetch
    refetch,       // forcer un nouveau fetch
    invalidate,    // invalider le cache
  } = useCachedQuery({
    db,
    store: 'cache',         // nom du store IndexedDB (schéma: 'key')
    key: 'items-list',      // clé de cache unique
    fetchFn: () => api.get('items'),  // fonction de récupération
    strategy: 'networkFirst',         // stratégie de cache
    ttl: 3600000,           // durée de vie du cache (1h par défaut)
    staleTime: 60000,       // durée avant que le cache soit "stale" (1min par défaut)
    enabled: true,          // activer/désactiver le fetch
  });

  if (isLoading) return <Spinner />;

  return (
    <List>
      {data?.map(item => (
        <ListItem key={item.id}>{item.label}</ListItem>
      ))}
    </List>
  );
};
```

### Stratégies disponibles

| Stratégie | Constante | Comportement |
| --- | --- | --- |
| `networkFirst` | `NETWORK_FIRST` | Réseau d'abord, cache en fallback (défaut) |
| `cacheFirst` | `CACHE_FIRST` | Cache d'abord, réseau si absent ou expiré |
| `swr` | `STALE_WHILE_REVALIDATE` | Retourne le cache immédiatement, revalide en arrière-plan |

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

// Utilisation avec constante
useCachedQuery({
  // ...
  strategy: CACHE_STRATEGIES.STALE_WHILE_REVALIDATE,
});
```

## useDb (IndexedDB)

Pour stocker de grandes quantités de données ou des données structurées complexes, utilisez `useDb` basé sur Dexie.

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

// Définir la base de données
const db = useDb({
  name: 'myApp',
  version: 1,
  stores: {
    items: 'id++, name, category, createdAt',
    categories: 'id++, name'
  },
  debug: true
});

export const ItemsManager = () => {
  const [items, setItems] = useState([]);

  // Charger les items
  useEffect(() => {
    db.items.toArray().then(setItems);
  }, []);

  // Ajouter un item
  const addItem = async (item) => {
    const id = await db.items.add(item);
    console.log('Item added with id:', id);
  };

  // Modifier un item
  const updateItem = async (id, changes) => {
    await db.items.update(id, changes);
  };

  // Supprimer un item
  const deleteItem = async (id) => {
    await db.items.delete(id);
  };

  // Requête avec filtre
  const getByCategory = async (category) => {
    return db.items.where('category').equals(category).toArray();
  };

  return (/* ... */);
};
```

### Fonctionnalités automatiques

`useDb` ajoute automatiquement :
- `createdAt` : timestamp de création
- `updatedAt` : timestamp de dernière modification
- Table `logs` : journal de toutes les opérations (create, update, delete)

### Requêtes Dexie

```
// Tous les items
const all = await db.items.toArray();

// Par clé primaire
const item = await db.items.get(1);

// Filtrer
const filtered = await db.items
  .where('category')
  .equals('electronics')
  .toArray();

// Trier
const sorted = await db.items
  .orderBy('createdAt')
  .reverse()
  .toArray();

// Limiter
const first10 = await db.items.limit(10).toArray();

// Compter
const count = await db.items.count();
```

> [!TIP]
> Pour un cache API avec stratégies automatiques, préférez `useCachedQuery`. Pour la synchronisation offline complète avec gestion de conflits, utilisez `useSyncClient`.

## Redux (méthode classique)

Si vous préférez utiliser Redux directement, SmartCommon fournit `ReduxProvider`.

### Créer un slice

```
// src/redux/reducers/sessionSlice.js

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  data: JSON.parse(sessionStorage.getItem("session")) ?? null
};

const sessionSlice = createSlice({
  name: "session",
  initialState,
  reducers: {
    setSession(state, action) {
      state.data = action.payload;
      sessionStorage.setItem("session", JSON.stringify(action.payload));
    },
    unsetSession(state) {
      state.data = null;
      sessionStorage.removeItem("session");
    }
  },
});

export default sessionSlice.reducer;
export const { setSession, unsetSession } = sessionSlice.actions;
```

### Configurer le store

```
// src/redux/index.js

import sessionReducer from "./reducers/sessionSlice";
import { combineReducers } from "redux";
import { configureStore } from "@reduxjs/toolkit";

const rootReducer = combineReducers({
  session: sessionReducer
});

export const reduxStore = configureStore({ reducer: rootReducer });
export * from "./reducers/sessionSlice";
```

### Utiliser dans les composants

```
import { useSelector, useDispatch } from "react-redux";
import { setSession, unsetSession } from "../../../redux";

export const MyComponent = () => {
  const session = useSelector(state => state.session.data);
  const dispatch = useDispatch();

  const login = (data) => dispatch(setSession(data));
  const logout = () => dispatch(unsetSession());

  return (/* ... */);
};
```

## Comparaison des méthodes

| Méthode | Cas d'usage | Persistance |
| --- | --- | --- |
| `useGlobalStates` | Session utilisateur, préférences | localStorage / sessionStorage |
| `useStates` | État de formulaire, UI locale | Mémoire (non persistant) |
| `useDb` | Données structurées, stockage local | IndexedDB |
| `useCachedQuery` | Cache API avec stratégies | IndexedDB |
| `useSyncClient` | Synchronisation offline complète | IndexedDB |
| Redux | Applications complexes, middleware | Configurable |

> [!TIP]
> Pour la plupart des cas, `useGlobalStates` et `useStates` suffisent. Pour un stockage IndexedDB simple, utilisez `useDb`. Pour le cache de données API, utilisez `useCachedQuery`. Pour le mode offline complet avec synchronisation, utilisez `useSyncClient`.

## Voir aussi
- [Hooks](/front/hooks) - Documentation complète des hooks
- [Requêtes API](/front/requetes-api) - Combiner avec les appels API
