SmarMaker - Documentation
Docs» 15_training:module7-smartcommon-hooks:etat

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

snippet.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 :

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

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

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

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

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

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

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

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

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

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

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

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

Fonctions retournées

Le hook retourne un objet avec deux fonctions :

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

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

snippet.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 :

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

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

Utilisation

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

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

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

snippet.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 :

snippet.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 | Retour au module | Chapitre suivant : Utilitaires →

Previous Next

Made with ❤ by CAP-REL · SmartMaker · GNU AGPL v3+
Code source · Faire un don
SmarMaker - Documentation
Traductions de cette page:
  • Français
  • Deutsch
  • English
  • Español
  • Italiano
  • Nederlands

Table of Contents

Table des matières

  • Chapitre 2 : Gestion d'état
    • useGlobalStates
      • Syntaxe de base
      • Configuration de la persistance
      • Lecture et écriture
      • Exemple : Panier d'achat
    • useStates
      • Syntaxe de base
      • Méthodes disponibles
      • Path notation
      • Exemple : Page de détail
    • useForm
      • Syntaxe de base
      • Avec valeurs initiales
      • Validation manuelle avec setField
    • Comparaison des hooks d'état
    • Bonnes pratiques
      • 1. Choisir le bon hook
      • 2. Organiser l'état global
    • useConfirm
      • Import
      • Fonctions retournées
      • Utilisation de confirm
      • Utilisation de alert
      • Options
      • Types et icônes
      • Prérequis
    • usePWAUpdate
      • Import
      • Utilisation
      • Avec rechargement automatique
      • Options
      • Valeurs retournées
    • UpdatePrompt
      • Import
      • Variantes d'affichage
      • Utilisation directe
      • Via le Provider
      • Props
      • Labels par défaut
    • Points clés à retenir
  • SmartAuth
  • SmartMaker - Back (PHP)
    • Mapping Dolibarr - React
  • SmartMaker - Front (React)
    • Animations de pages
    • Architecture
    • Astuces
    • Calendar
    • Composants et pages
    • Configuration du Provider
    • Debug et Logs
    • Hooks SmartCommon
    • PWA (Progressive Web App)
    • Requêtes API
    • Routage
    • SmartCommon
    • Stockage de données
    • Synchronisation offline
    • Thèmes
    • Traductions
  • HowTo - Pas à pas - Votre première application
    • Développement PHP (back)
    • Développement React (front)
    • Première étape : Module Builder Dolibarr
    • SmartAuth
    • SmartBoot : Un squelette prêt à l'emploi
  • Formation SmartMaker
    • Module 1 : Fondamentaux JavaScript ES6+
      • Chapitre 1 : Variables et Scope
      • Chapitre 2 : Fonctions
      • Chapitre 3 : Programmation Asynchrone
      • Chapitre 4 : Modules ES6
    • Module 2 : Introduction à React
      • Chapitre 1 : Philosophie React
      • Chapitre 2 : JSX
      • Chapitre 3 : Composants
    • Module 3 : Hooks React Fondamentaux
      • Chapitre 1 : useState
      • Chapitre 2 : useEffect
      • Chapitre 3 : useRef
      • Chapitre 4 : useContext
    • Module 4 : React Avancé
      • Chapitre 1 : useCallback et useMemo
      • Chapitre 2 : Custom Hooks
      • Chapitre 3 : Redux et Redux Toolkit
    • Module 5 : Architecture SmartMaker
      • Chapitre 1 : Structure du projet
      • Chapitre 2 : Configuration
      • Chapitre 3 : Flux de données
    • Module 6 : SmartCommon - Composants
      • Chapitre 1 : Mise en page
      • Chapitre 2 : Navigation
      • Chapitre 3 : Formulaires
      • Chapitre 4 : Affichage
    • Module 7 : SmartCommon - Hooks
      • Chapitre 1 : useApi
      • Chapitre 2 : Gestion d'état
      • Chapitre 3 : Hooks utilitaires
      • Chapitre 4 : Synchronisation Offline
    • Module 8 : Backend API (PHP)
      • Chapitre 1 : Routage
      • Chapitre 2 : Controllers
      • Chapitre 3 : Mappers
      • Extrafields et formulaires dynamiques
    • Module 9 : Intégration complète
      • Chapitre 1 : Backend
      • Chapitre 2 : Frontend
      • Chapitre 3 : Déploiement
    • Module 10 : Fonctionnalités avancées
      • Chapitre 1 : Mode offline
      • Chapitre 2 : Internationalisation (i18n)
      • Chapitre 3 : Autres fonctionnalités
    • Module 11 : Bonnes pratiques
  • Démonstration
  • Start
  • Composants et pages
  • Afficher le texte source
  • Anciennes révisions
  • Liens de retour
  • Haut de page