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 gst = useGlobalStates(); return ( <div> <p>Utilisateur : {gst.get('session')?.user?.name}</p> <p>Langue : {gst.get('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 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> ); }
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>; }
É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'; 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> ); }
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> ); }
// 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
| 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 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);
// 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: [] } }
Hook pour afficher des dialogues de confirmation et d'alerte modaux.
import { useConfirm } from '@cap-rel/smartcommon';
Le hook retourne un objet avec deux fonctions :
const { confirm, alert } = useConfirm();
true ou falsetruefunction 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>; }
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>; }
| 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 |
| 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) |
Le composant ConfirmProvider doit envelopper l'application :
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".
Hook pour détecter et gérer les mises à jour de l'application PWA.
import { usePWAUpdate } from '@cap-rel/smartcommon';
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> ); }
function App() { usePWAUpdate({ autoReload: true, onUpdateAvailable: () => { console.log('Mise à jour disponible'); }, onUpdateActivated: () => { console.log('Mise à jour activée'); } }); return <MyApp />; }
| 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 |
| 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 |
Composant UI prêt à l'emploi qui encapsule usePWAUpdate et affiche une notification quand une mise à jour est disponible.
import { UpdatePrompt } from '@cap-rel/smartcommon';
Trois variantes sont disponibles :
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> ); }
Le Provider de SmartCommon accepte une prop pwaUpdate qui intègre automatiquement UpdatePrompt :
import { Provider } from '@cap-rel/smartcommon'; import { config } from './appConfig'; export const App = () => ( <Provider config={config} pwaUpdate={{ variant: 'toast', checkInterval: 300000 }} > <Router /> </Provider> );
| 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 |
| Clé | Valeur par défaut |
|---|---|
| title | “Mise à jour disponible” |
| message | “Une nouvelle version est disponible.” |
| reloadButton | “Rafraîchir” |
| dismissButton | “Plus tard” |
user.address.city, items[0], items[]← Chapitre précédent | Retour au module | Chapitre suivant : Utilitaires →