SmartCommon propose plusieurs hooks pour gérer l'état selon les besoins :
État global accessible partout, avec persistance automatique.
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> ); }
Dans appConfig.js :
export const config = { storage: { local: ["session", "settings"] // Persisté en localStorage }, globalState: { reducers: { session: null, settings: { lng: "fr", theme: "light" }, cart: { items: [], total: 0 } } } };
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> ); }
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>; }
État local avec notation par chemin (path notation).
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é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 |
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
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> ); };
Hook spécialisé pour les formulaires avec validation.
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> ); }
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> ); }
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'] } );
| 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 |
// 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);
// 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: [] } }
user.address.city, items[0], items[]← Chapitre précédent | Retour au module | Chapitre suivant : Utilitaires →