# 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 [session, setSession] = useGlobalStates('session');
const [settings, setSettings] = useGlobalStates('settings');
return (
Utilisateur : {session?.user?.name}
Langue : {settings?.lng}
);
}
```
### 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 [settings, setSettings] = useGlobalStates('settings');
const changeLanguage = (lng) => {
setSettings({ ...settings, lng });
};
const changeTheme = (theme) => {
setSettings({ ...settings, theme });
};
return (
changeLanguage(e.target.value)}
>
Français
English
changeTheme('light')}>Clair
changeTheme('dark')}>Sombre
);
}
```
### Exemple : Panier d'achat
```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 (
{product.label}
{product.price} €
addItem(product)}>
Ajouter au panier
);
}
function CartIcon() {
const { cart } = useCart();
const itemCount = cart.items.reduce((sum, i) => sum + i.quantity, 0);
return 🛒 {itemCount} ;
}
```
## 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 (
Count: {st.get('count')}
st.set('count', st.get('count') + 1)}>
+1
);
}
```
### 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 ;
}
if (st.get('error')) {
return Erreur : {st.get('error')} ;
}
const product = st.get('product');
return (
Prix : {product.price} €
Stock : {product.stock}
);
};
```
## 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';
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 (
);
}
```
### Avec valeurs initiales
```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 (
);
}
```
### Validation Zod
```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
```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
```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
[[:15_training:module7-smartcommon-hooks:useapi|← Chapitre précédent]] | [[:15_training:module7-smartcommon-hooks:start|Retour au module]] | [[:15_training:module7-smartcommon-hooks:utilitaires|Chapitre suivant : Utilitaires →]]