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 [session, setSession] = useGlobalStates('session');
    const [settings, setSettings] = useGlobalStates('settings');
 
    return (
        <div>
            <p>Utilisateur : {session?.user?.name}</p>
            <p>Langue : {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 [settings, setSettings] = useGlobalStates('settings');
 
    const changeLanguage = (lng) => {
        setSettings({ ...settings, lng });
    };
 
    const changeTheme = (theme) => {
        setSettings({ ...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 [cart, setCart] = useGlobalStates('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
        );
 
        setCart({ 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
        );
        setCart({ items: newItems, total });
    };
 
    const clearCart = () => {
        setCart({ 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';
import { z } from 'zod';
 
const schema = z.object({
    email: z.string().email('Email invalide'),
    password: z.string().min(6, 'Minimum 6 caractères')
});
 
function LoginForm() {
    const form = useForm({ schema });
 
    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.formState.isSubmitting}>
                Connexion
            </Button>
        </Form>
    );
}

Avec valeurs initiales

snippet.javascript
function EditProductForm({ product }) {
    const form = useForm({
        schema: productSchema,
        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 Zod

snippet.javascript
import { z } from 'zod';
 
const productSchema = z.object({
    label: z.string()
        .min(2, 'Minimum 2 caractères')
        .max(100, 'Maximum 100 caractères'),
 
    price: z.number()
        .min(0, 'Le prix doit être positif')
        .max(99999, 'Prix trop élevé'),
 
    description: z.string().optional(),
 
    category: z.enum(['electronics', 'clothing', 'food'], {
        errorMap: () => ({ message: 'Catégorie invalide' })
    }),
 
    stock: z.number().int('Doit être un entier').min(0),
 
    isActive: z.boolean().default(true)
});
 
// Validation conditionnelle
const orderSchema = z.object({
    deliveryType: z.enum(['standard', 'express']),
    expressDate: z.string().optional()
}).refine(
    data => data.deliveryType !== 'express' || data.expressDate,
    { message: 'Date requise pour livraison express', path: ['expressDate'] }
);

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 [session] = useGlobalStates('session');
 
// État de chargement d'une page → useStates
const st = useStates({ initialStates: { loading: true, data: null } });
 
// Formulaire → useForm
const form = useForm({ schema });
 
// 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: []
    }
}

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 Zod
  4. Path notation : user.address.city, items[0], items[]
  5. 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 Zod
    • Comparaison des hooks d'état
    • Bonnes pratiques
      • 1. Choisir le bon hook
      • 2. Organiser l'état global
    • 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
    • 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" quasiment 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
    • 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