Chapitre 3 : Hooks utilitaires
SmartCommon fournit des hooks utilitaires pour les tâches courantes.
useNavigation
Navigation programmatique avec React Router.
import { useNavigation } from '@cap-rel/smartcommon';
function MyComponent() {
const nav = useNavigation();
return (
<div>
{/* Navigation vers une route */}
<button onClick={() => nav.navigate('/products')}>
Produits
</button>
{/* Navigation avec paramètre */}
<button onClick={() => nav.navigate(`/products/${id}`)}>
Détails
</button>
{/* Retour arrière */}
<button onClick={() => nav.navigate(-1)}>
Retour
</button>
{/* Remplacement (sans historique) */}
<button onClick={() => nav.replace('/login').to()}>
Login
</button>
</div>
);
}
useLibConfig
Accès à la configuration de l'application.
import { useLibConfig } from '@cap-rel/smartcommon';
function DebugInfo() {
const config = useLibConfig();
if (!config.debug) return null;
return (
<div className="debug-panel">
<p>API URL: {config.api.prefixUrl}</p>
<p>Mode: {config.debug ? 'Debug' : 'Production'}</p>
</div>
);
}
useIntl
Formatage de dates et nombres avec l'API Intl.
Formatage de dates
import { useIntl } from '@cap-rel/smartcommon';
function EventCard({ event }) {
const intl = useIntl();
// Format par défaut
const dateFormatted = intl.DateTimeFormat(event.date);
// "15/03/2024, 14:30:00"
// Format personnalisé
const dateOnly = intl.DateTimeFormat(event.date, 'fr-FR', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
// "15 mars 2024"
// Heure seule
const timeOnly = intl.DateTimeFormat(event.date, 'fr-FR', {
hour: '2-digit',
minute: '2-digit'
});
// "14:30"
return (
<div>
<h3>{event.title}</h3>
<p>Le {dateOnly} à {timeOnly}</p>
</div>
);
}
Formatage de nombres
function PriceDisplay({ price, currency = 'EUR' }) {
const intl = useIntl();
const formatted = intl.NumberFormat(price, 'fr-FR', {
style: 'currency',
currency: currency
});
// "29,99 €"
return <span>{formatted}</span>;
}
function PercentDisplay({ value }) {
const intl = useIntl();
const formatted = intl.NumberFormat(value, 'fr-FR', {
style: 'percent',
minimumFractionDigits: 1
});
// "15,5 %"
return <span>{formatted}</span>;
}
useWindow
Informations sur la fenêtre du navigateur.
import { useWindow } from '@cap-rel/smartcommon';
function ResponsiveLayout({ children }) {
const { orientation, windowDimension, darkMode } = useWindow();
// orientation: 'portrait' ou 'landscape'
// windowDimension: { w, h }
// darkMode: boolean
if (windowDimension.w < 768) {
return <MobileLayout>{children}</MobileLayout>;
}
if (windowDimension.w < 1024) {
return <TabletLayout>{children}</TabletLayout>;
}
return <DesktopLayout>{children}</DesktopLayout>;
}
function WindowInfo() {
const { windowDimension } = useWindow();
return <p>Fenêtre : {windowDimension.w} x {windowDimension.h}</p>;
}
useFile
Utilitaires pour la manipulation de fichiers.
Redimensionner une image
import { useFile } from '@cap-rel/smartcommon';
function ImageUploader({ onUpload }) {
const { resizeImage } = useFile();
const handleFileChange = async (e) => {
const file = e.target.files[0];
if (!file) return;
// Redimensionner avant upload
const base64 = await resizeImage(file, {
maxWidth: 1920,
maxHeight: 1080,
quality: 85
});
// base64 = "data:image/jpeg;base64,..."
onUpload(base64);
};
return (
<input
type="file"
accept="image/*"
onChange={handleFileChange}
/>
);
}
useDb
Base de données IndexedDB via Dexie pour le stockage local de grandes quantités de données.
Configuration
import { useDb } from '@cap-rel/smartcommon';
const db = useDb({
name: 'myApp',
version: 1,
stores: {
items: 'id++, name, category, createdAt',
logs: 'id++, action, timestamp'
},
debug: true
});
Opérations CRUD
function ItemsManager() {
const db = useDb({
name: 'myApp',
version: 1,
stores: {
items: 'id++, name, category'
}
});
// Créer
const addItem = async (item) => {
const id = await db.items.add(item);
console.log('Item créé avec id:', id);
return id;
};
// Lire tous
const getAllItems = async () => {
return db.items.toArray();
};
// Lire un
const getItem = async (id) => {
return db.items.get(id);
};
// Modifier
const updateItem = async (id, changes) => {
await db.items.update(id, changes);
};
// Supprimer
const deleteItem = async (id) => {
await db.items.delete(id);
};
// ...
}
Requêtes avancées
// Filtrer par valeur
const electronics = await db.items
.where('category')
.equals('electronics')
.toArray();
// Filtrer par plage
const expensiveItems = await db.items
.where('price')
.above(100)
.toArray();
// Trier
const sorted = await db.items
.orderBy('createdAt')
.reverse()
.toArray();
// Limiter
const first10 = await db.items
.limit(10)
.toArray();
// Compter
const count = await db.items.count();
// Recherche texte
const matching = await db.items
.filter(item => item.name.includes('test'))
.toArray();
Exemple : Mode offline
import { useEffect } from 'react';
import { useApi, useDb, useStates } from '@cap-rel/smartcommon';
function OfflineCapableList() {
const api = useApi();
const db = useDb({
name: 'myApp',
version: 1,
stores: { items: 'id, name, synced' }
});
const st = useStates({
initialStates: { items: [], loading: true }
});
useEffect(() => {
loadItems();
}, []);
const loadItems = async () => {
try {
// Essayer de charger depuis l'API
const data = await api.private.get('items').json();
// Sauvegarder en local
await db.items.clear();
await db.items.bulkAdd(data.map(i => ({ ...i, synced: true })));
st.set('items', data);
} catch (err) {
// En cas d'erreur réseau, charger depuis IndexedDB
console.log('Mode offline - chargement local');
const localItems = await db.items.toArray();
st.set('items', localItems);
} finally {
st.set('loading', false);
}
};
const addItem = async (item) => {
// Sauvegarder localement d'abord
const id = await db.items.add({ ...item, synced: false });
// Mettre à jour l'UI
st.set('items', [...st.get('items'), { ...item, id }]);
// Tenter de synchroniser
try {
const result = await api.private.post('items', { json: item }).json();
await db.items.update(id, { ...result, synced: true });
} catch (err) {
console.log('Sync échouée, sera retentée plus tard');
}
};
// ...
}
useAnimation
Gestion d'animations avec Framer Motion.
import { useAnimation } from '@cap-rel/smartcommon';
import { motion } from 'framer-motion';
function AnimatedList({ items }) {
const { start } = useAnimation();
return (
<div>
{items.map((item, index) => (
<motion.div
key={item.id}
initial={{ opacity: 0, y: 20 }}
animate={start ? { opacity: 1, y: 0 } : {}}
transition={{ delay: index * 0.1 }}
>
{item.name}
</motion.div>
))}
</div>
);
}
useListDnD
Drag and drop pour réordonner des listes.
import { useListDnD } from '@cap-rel/smartcommon';
function SortableList({ initialItems, onReorder }) {
const {
items,
onDragStart,
onDragOver,
onDrop
} = useListDnD({
initialItems,
onReorder: (newItems) => {
console.log('Nouvel ordre:', newItems);
onReorder(newItems);
}
});
return (
<ul>
{items.map((item, index) => (
<li
key={item.id}
draggable
onDragStart={(e) => onDragStart(e, index)}
onDragOver={onDragOver}
onDrop={(e) => onDrop(e, index)}
>
{item.name}
</li>
))}
</ul>
);
}
Tableau récapitulatif
| Hook | Usage | Exemple |
|---|---|---|
| useNavigation | Navigation | nav.navigate('/products') |
| useLibConfig | Config app | config.debug |
| useIntl | Formatage | intl.DateTimeFormat(date) |
| useWindow | Responsive | orientation, windowDimension, scroll, darkMode |
| useFile | Fichiers | resizeImage(file, options) |
| useDb | IndexedDB | db.items.add(item) |
| useAnimation | Animations | start (boolean) |
| useListDnD | Drag & drop | onDragStart, onDragOver, onDrop |
Points clés à retenir
- useNavigation pour la navigation programmatique
- useIntl pour le formatage localisé
- useWindow pour le responsive design
- useFile pour le traitement d'images
- useDb pour le stockage local (offline)
← Chapitre précédent | Retour au module | Module suivant : Backend API ->