---
title: "Chapitre 2 : Gestion d'état"
weight: 590
---

# Chapitre 2 : Gestion d'état

SmartCommon propose plusieurs hooks pour gérer l'état selon les besoins :

- **useGlobalStates** : état partagé entre composants avec persistance
- **useStates** : état local d'un composant
- **useForm** : état spécialisé pour les formulaires

## useGlobalStates

État global accessible partout, avec persistance automatique.

### Syntaxe de base

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

function MyComponent() {
    const gst = useGlobalStates();

    return (
        <div>
            <p>Utilisateur : {gst.get('session')?.user?.name}</p>
            <p>Langue : {gst.get('settings')?.lng}</p>
        </div>
    );
}
```

### Configuration de la persistance

Dans `appConfig.js` :

```javascript
export const config = {
    storage: {
        local: ["session", "settings"]  // Persisté en localStorage
    },

    globalState: {
        reducers: {
            session: null,
            settings: { lng: "fr", theme: "light" },
            cart: { items: [], total: 0 }
        }
    }
};
```

### Lecture et écriture

```javascript
function SettingsPage() {
    const gst = useGlobalStates();
    const settings = gst.get('settings');

    const changeLanguage = (lng) => {
        gst.local.set('settings', { ...settings, lng });
    };

    const changeTheme = (theme) => {
        gst.local.set('settings', { ...settings, theme });
    };

    return (
        <div>
            <select
                value={settings?.lng}
                onChange={(e) => changeLanguage(e.target.value)}
            >
                <option value="fr">Français</option>
                <option value="en">English</option>
            </select>

            <button onClick={() => changeTheme('light')}>Clair</button>
            <button onClick={() => changeTheme('dark')}>Sombre</button>
        </div>
    );
}
```

### Exemple : Panier d'achat

```javascript
function useCart() {
    const gst = useGlobalStates();
    const cart = gst.get('cart');

    const addItem = (product) => {
        const existing = cart.items.find(i => i.id === product.id);

        let newItems;
        if (existing) {
            newItems = cart.items.map(i =>
                i.id === product.id
                    ? { ...i, quantity: i.quantity + 1 }
                    : i
            );
        } else {
            newItems = [...cart.items, { ...product, quantity: 1 }];
        }

        const total = newItems.reduce(
            (sum, i) => sum + i.price * i.quantity,
            0
        );

        gst.set('cart', { items: newItems, total });
    };

    const removeItem = (productId) => {
        const newItems = cart.items.filter(i => i.id !== productId);
        const total = newItems.reduce(
            (sum, i) => sum + i.price * i.quantity,
            0
        );
        gst.set('cart', { items: newItems, total });
    };

    const clearCart = () => {
        gst.set('cart', { items: [], total: 0 });
    };

    return { cart, addItem, removeItem, clearCart };
}

// Utilisation
function ProductCard({ product }) {
    const { addItem } = useCart();

    return (
        <div>
            <h3>{product.label}</h3>
            <p>{product.price} €</p>
            <button onClick={() => addItem(product)}>
                Ajouter au panier
            </button>
        </div>
    );
}

function CartIcon() {
    const { cart } = useCart();
    const itemCount = cart.items.reduce((sum, i) => sum + i.quantity, 0);

    return <span>🛒 {itemCount}</span>;
}
```

## useStates

État local avec notation par chemin (path notation).

### Syntaxe de base

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

function MyComponent() {
    const st = useStates({
        initialStates: {
            count: 0,
            user: { name: '', email: '' },
            items: [],
            loading: false
        },
        debug: true  // Affiche les changements en console
    });

    return (
        <div>
            <p>Count: {st.get('count')}</p>
            <button onClick={() => st.set('count', st.get('count') + 1)}>
                +1
            </button>
        </div>
    );
}
```

### Méthodes disponibles

| Méthode | Description |
| --- | --- |
| st.get(path) | Lire une valeur |
| st.set(path, value) | Écrire une valeur |
| st.unset(path) | Supprimer une valeur |
| st.values | Objet contenant toutes les valeurs |

### Path notation

```javascript
const st = useStates({
    initialStates: {
        user: { name: '', address: { city: '' } },
        items: []
    }
});

// Lecture
st.get('user');                // { name: '', address: { city: '' } }
st.get('user.name');           // ''
st.get('user.address.city');   // ''
st.get('items');               // []
st.get('items[0]');            // undefined

// Écriture
st.set('user.name', 'Jean');
st.set('user.address.city', 'Paris');

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

// Manipulation de tableaux
st.set('items[]', { id: 1 });     // Push
st.set('items[0].name', 'test');  // Modifier index
st.unset('items[0]');             // Supprimer index
```

### Exemple : Page de détail

```javascript
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Page, Block, Spinner } from '@cap-rel/smartcommon';
import { useApi, useStates } from '@cap-rel/smartcommon';

export const ProductDetailPage = () => {
    const { id } = useParams();
    const api = useApi();

    const st = useStates({
        initialStates: {
            product: null,
            loading: true,
            error: null,
            isEditing: false
        }
    });

    useEffect(() => {
        loadProduct();
    }, [id]);

    const loadProduct = async () => {
        st.set('loading', true);
        st.set('error', null);

        try {
            const data = await api.private.get(`products/${id}`).json();
            st.set('product', data);
        } catch (err) {
            st.set('error', err.message);
        } finally {
            st.set('loading', false);
        }
    };

    if (st.get('loading')) {
        return <Page><Spinner /></Page>;
    }

    if (st.get('error')) {
        return <Page><Block>Erreur : {st.get('error')}</Block></Page>;
    }

    const product = st.get('product');

    return (
        <Page title={product.label}>
            <Block>
                <p>Prix : {product.price} €</p>
                <p>Stock : {product.stock}</p>
            </Block>
        </Page>
    );
};
```

## useForm

Hook spécialisé pour les formulaires avec validation.

### Syntaxe de base

```javascript
import { useForm } from '@cap-rel/smartcommon';
import { Form, Input, Button } from '@cap-rel/smartcommon';

function LoginForm() {
    const form = useForm({ defaultValues: { email: '', password: '' } });

    const handleSubmit = async (data) => {
        console.log('Données validées:', data);
    };

    return (
        <Form form={form} onSubmit={handleSubmit}>
            <Input name="email" label="Email" type="email" />
            <Input name="password" label="Mot de passe" type="password" />
            <Button type="submit" loading={form.isFormSubmitting}>
                Connexion
            </Button>
        </Form>
    );
}
```

### Avec valeurs initiales

```javascript
function EditProductForm({ product }) {
    const form = useForm({
        defaultValues: {
            label: product.label,
            price: product.price,
            description: product.description
        }
    });

    const handleSubmit = async (data) => {
        await api.private.put(`products/${product.id}`, { json: data });
    };

    return (
        <Form form={form} onSubmit={handleSubmit}>
            <Input name="label" label="Nom" />
            <Input name="price" label="Prix" type="number" />
            <Textarea name="description" label="Description" />
            <Button type="submit">Enregistrer</Button>
        </Form>
    );
}
```

### Validation manuelle avec setField

```javascript
// useForm ne fait pas de validation automatique.
// Utilisez setField pour gérer les erreurs manuellement :

form.setField({
    name: 'email',
    value: inputValue,
    errors: {
        required: { condition: !inputValue },
        format: { condition: inputValue && !isValidEmail(inputValue) }
    }
});

// Vérifier les erreurs
const hasError = form.get('errors.email.required'); // true | false
```

## Comparaison des hooks d'état

| Hook | Portée | Persistance | Cas d'usage |
| --- | --- | --- | --- |
| useGlobalStates | Application | localStorage | Session, préférences, panier |
| useStates | Composant | Non | État de page, chargement |
| useForm | Composant | Non | Formulaires avec validation |
| useState (React) | Composant | Non | État simple |

## Bonnes pratiques

### 1. Choisir le bon hook

```javascript
// Session utilisateur -> useGlobalStates
const gst = useGlobalStates();
const session = gst.get('session');

// État de chargement d'une page -> useStates
const st = useStates({ initialStates: { loading: true, data: null } });

// Formulaire -> useForm
const form = useForm({ defaultValues: { name: '', email: '' } });

// Toggle simple -> useState
const [isOpen, setIsOpen] = useState(false);
```

### 2. Organiser l'état global

```javascript
// appConfig.js
globalState: {
    reducers: {
        // Auth
        session: null,

        // Préférences utilisateur
        settings: { lng: 'fr', theme: 'light' },

        // Données métier globales
        cart: { items: [], total: 0 },

        // Cache
        categories: []
    }
}
```

## useConfirm

Hook pour afficher des dialogues de confirmation et d'alerte modaux.

### Import

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

### Fonctions retournées

Le hook retourne un objet avec deux fonctions :

```javascript
const { confirm, alert } = useConfirm();
```

- **confirm** : affiche un dialogue avec boutons Confirmer / Annuler, retourne `true` ou `false`
- **alert** : affiche un dialogue avec un seul bouton OK, retourne toujours `true`

### Utilisation de confirm

```javascript
function DeleteButton({ item, onDelete }) {
    const { confirm } = useConfirm();

    const handleDelete = async () => {
        const confirmed = await confirm({
            type: 'delete',
            title: 'Supprimer cet élément ?',
            message: `Voulez-vous vraiment supprimer "${item.name}" ?`,
            detail: item.ref,
            confirmText: 'Supprimer',
            cancelText: 'Annuler'
        });

        if (confirmed) {
            onDelete(item.id);
        }
    };

    return <button onClick={handleDelete}>Supprimer</button>;
}
```

### Utilisation de alert

```javascript
function SaveButton({ onSave }) {
    const { alert } = useConfirm();

    const handleSave = async () => {
        try {
            await onSave();
            await alert({
                type: 'info',
                title: 'Succès',
                message: 'Les données ont été enregistrées.'
            });
        } catch (err) {
            await alert({
                type: 'warning',
                title: 'Erreur',
                message: err.message
            });
        }
    };

    return <button onClick={handleSave}>Enregistrer</button>;
}
```

### Options

| Option | Type | Description |
| --- | --- | --- |
| type | string | Type de dialogue : `'danger'`, `'delete'`, `'warning'`, `'info'` |
| title | string | Titre du dialogue |
| message | string | Message de confirmation |
| detail | string | Texte complémentaire affiché dans un encadré gris |
| confirmText | string | Texte du bouton de confirmation |
| cancelText | string | Texte du bouton d'annulation |

### Types et icônes

| Type | Icône | Couleur du bouton |
| --- | --- | --- |
| danger | Corbeille (rouge) | Rouge |
| delete | Corbeille (rouge) | Rouge |
| warning | Triangle exclamation (orange) | Orange |
| info | Info (bleu) | Bleu |
| (autre) | Point d'interrogation (gris) | Bleu (défaut) |

### Prérequis

Le composant `ConfirmProvider` doit envelopper l'application :

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

function App() {
    return (
        <ConfirmProvider labels={{ cancel: 'Annuler', confirm: 'OK' }}>
            <MyApp />
        </ConfirmProvider>
    );
}
```

La prop `labels` permet de définir les textes par défaut des boutons. Si non fournie, les valeurs sont `"Cancel"` et `"OK"`.

## usePWAUpdate

Hook pour détecter et gérer les mises à jour de l'application PWA.

### Import

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

### Utilisation

```javascript
function UpdateBanner() {
    const {
        updateAvailable,
        updateActivated,
        checkForUpdates,
        applyUpdate
    } = usePWAUpdate({
        checkInterval: 300000  // Vérifier toutes les 5 min
    });

    if (!updateAvailable) return null;

    return (
        <div className="bg-blue-500 text-white p-4">
            <p>Une mise à jour est disponible</p>
            <button onClick={applyUpdate}>
                Mettre à jour maintenant
            </button>
        </div>
    );
}
```

### Avec rechargement automatique

```javascript
function App() {
    usePWAUpdate({
        autoReload: true,
        onUpdateAvailable: () => {
            console.log('Mise à jour disponible');
        },
        onUpdateActivated: () => {
            console.log('Mise à jour activée');
        }
    });

    return <MyApp />;
}
```

### Options

| Option | Type | Défaut | Description |
| --- | --- | --- | --- |
| autoReload | boolean | false | Recharger automatiquement après mise à jour |
| checkInterval | number | 0 | Intervalle de vérification en ms (0 = désactivé) |
| onUpdateAvailable | function | - | Callback quand une mise à jour est disponible |
| onUpdateActivated | function | - | Callback quand la mise à jour est activée |

### Valeurs retournées

| Propriété | Type | Description |
| --- | --- | --- |
| updateAvailable | boolean | Mise à jour en attente |
| updateActivated | boolean | Mise à jour activée |
| registration | object | ServiceWorkerRegistration |
| checkForUpdates | function | Vérifier manuellement |
| applyUpdate | function | Appliquer la mise à jour |
| reloadPage | function | Recharger la page |

## UpdatePrompt

Composant UI prêt à l'emploi qui encapsule `usePWAUpdate` et affiche une notification quand une mise à jour est disponible.

### Import

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

### Variantes d'affichage

Trois variantes sont disponibles :

- **toast** (défaut) : notification discrète via react-hot-toast
- **banner** : bandeau fixe en haut ou en bas de l'écran
- **modal** : dialogue modal centré

### Utilisation directe

```javascript
function App() {
    return (
        <div>
            <MyApp />
            <UpdatePrompt
                variant="banner"
                position="bottom"
                checkInterval={300000}
                labels={{
                    title: 'Nouvelle version',
                    message: 'Une mise à jour est disponible.',
                    reloadButton: 'Rafraîchir',
                    dismissButton: 'Plus tard'
                }}
            />
        </div>
    );
}
```

### Via le Provider

Le `Provider` de SmartCommon accepte une prop `pwaUpdate` qui intègre automatiquement `UpdatePrompt` :

```javascript
import { Provider } from '@cap-rel/smartcommon';
import { config } from './appConfig';

export const App = () => (
    <Provider
        config={config}
        pwaUpdate={{
            variant: 'toast',
            checkInterval: 300000
        }}
    >
        <Router />
    </Provider>
);
```

### Props

| Prop | Type | Défaut | Description |
| --- | --- | --- | --- |
| variant | string | `"toast"` | `"toast"`, `"banner"` ou `"modal"` |
| position | string | `"bottom"` | Position du banner : `"top"` ou `"bottom"` |
| autoReload | boolean | `false` | Recharger automatiquement après activation |
| checkInterval | number | `0` | Intervalle de vérification en ms (0 = désactivé) |
| labels | object | - | Textes personnalisés (voir ci-dessous) |
| onUpdateAvailable | function | - | Callback quand une mise à jour est détectée |
| onUpdateActivated | function | - | Callback quand la mise à jour est activée |

### Labels par défaut

| Clé | Valeur par défaut |
| --- | --- |
| title | "Mise à jour disponible" |
| message | "Une nouvelle version est disponible." |
| reloadButton | "Rafraîchir" |
| dismissButton | "Plus tard" |

## Points clés à retenir

1. **useGlobalStates** pour les données partagées et persistées
2. **useStates** pour l'état local avec path notation
3. **useForm** pour les formulaires avec validation manuelle
4. **useConfirm** pour les dialogues de confirmation et d'alerte
5. **usePWAUpdate** pour détecter les mises à jour PWA
6. **UpdatePrompt** pour afficher une UI de mise à jour prête à l'emploi
7. **Path notation** : `user.address.city`, `items[0]`, `items[]`
8. Configurer **storage.local** pour la persistance

[← Chapitre précédent](/training/module7-smartcommon-hooks/useapi) | [Retour au module](/training/module7-smartcommon-hooks) | [Chapitre suivant : Utilitaires ->](/training/module7-smartcommon-hooks/utilitaires)
