Module 11 : Bonnes pratiques
Ce module final résume les bonnes pratiques pour le développement d'applications SmartMaker.
Organisation du code
Structure des composants
components/pages/private/TasksPage/
├── index.jsx # Composant principal (export)
├── TasksPage.jsx # Logique et rendu
├── components/ # Sous-composants locaux
│ ├── TaskList.jsx
│ └── TaskFilters.jsx
└── hooks/ # Hooks spécifiques à cette page
└── useTasks.js
Convention de nommage
- Composants : PascalCase (
TaskList,UserProfile) - Hooks : camelCase avec préfixe use (
useTasks,useAuth) - Fichiers : même nom que le composant (
TaskList.jsx) - Dossiers : PascalCase pour composants, camelCase pour hooks
Performance
Éviter les re-rendus inutiles
- snippet.javascript
// Éviter : objet recréé à chaque rendu <Component style={{ color: 'red' }} /> // Préférer : objet stable const style = useMemo(() => ({ color: 'red' }), []); <Component style={style} />
Mémoriser les callbacks
- snippet.javascript
// Pour les composants mémorisés const handleClick = useCallback((id) => { setSelected(id); }, []);
Lazy loading des pages
- snippet.javascript
import { lazy, Suspense } from 'react'; import { Spinner } from '@cap-rel/smartcommon'; const TasksPage = lazy(() => import('./pages/private/TasksPage')); <Suspense fallback={<Spinner />}> <TasksPage /> </Suspense>
Gestion d'état
Choisir le bon outil
| Besoin | Solution |
|---|---|
| État d'un formulaire | useStates ou useForm |
| État d'une page (loading, error) | useStates |
| Session utilisateur | useGlobalStates('session') |
| Préférences | useGlobalStates('settings') |
| Données partagées | useGlobalStates |
| Gros volumes, offline | useDb |
Structurer l'état global
- snippet.javascript
globalState: { reducers: { // Auth session: null, // Préférences settings: { lng: 'fr', theme: 'light' }, // Cache de données cache: { categories: [], lastFetch: null } } }
Appels API
Toujours gérer les erreurs
- snippet.javascript
const loadData = async () => { st.set('loading', true); st.set('error', null); try { const data = await api.private.get('items').json(); st.set('items', data.items); } catch (err) { st.set('error', err.message); // Log pour debug console.error('Load error:', err); } finally { st.set('loading', false); } };
Annuler les requêtes au démontage
- snippet.javascript
useEffect(() => { const controller = new AbortController(); const load = async () => { try { const data = await api.private.get('items', { signal: controller.signal }).json(); setItems(data); } catch (err) { if (err.name !== 'AbortError') { setError(err.message); } } }; load(); return () => controller.abort(); }, []);
Formulaires
Validation avec Zod
- snippet.javascript
import { z } from 'zod'; const schema = z.object({ email: z.string().email('Email invalide'), password: z.string().min(8, 'Minimum 8 caractères'), age: z.number().min(18, 'Doit être majeur').optional() });
Messages d'erreur clairs
- snippet.javascript
<Input name="email" label="Email" error={errors.email?.message} required />
Sécurité
Ne jamais stocker de secrets côté client
- snippet.javascript
// JAMAIS const API_KEY = 'secret123'; // OK : variables d'environnement serveur uniquement // Le client n'a accès qu'aux VITE_ prefixés const API_URL = import.meta.env.VITE_API_URL;
Valider côté serveur
Ne pas faire confiance aux données du client :
- snippet.php
// Controller PHP public function create($payload) { // Toujours valider if (empty($payload['label'])) { return ['Label required', 400]; } // Toujours vérifier les droits if (!$user->hasRight('mymodule', 'create')) { return ['Forbidden', 403]; } // Toujours échapper $label = $db->escape($payload['label']); }
Protéger contre XSS
React échappe automatiquement, mais attention à dangerouslySetInnerHTML :
- snippet.javascript
// Dangereux <div dangerouslySetInnerHTML={{ __html: userContent }} /> // Si nécessaire, sanitiser d'abord import DOMPurify from 'dompurify'; <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />
Tests
Tester les composants critiques
- snippet.javascript
// TasksPage.test.jsx import { render, screen, waitFor } from '@testing-library/react'; import { TasksPage } from './TasksPage'; test('affiche la liste des tâches', async () => { render(<TasksPage />); await waitFor(() => { expect(screen.getByText('Mes tâches')).toBeInTheDocument(); }); });
Debug
Utiliser les outils
- React DevTools : inspecter les composants et l'état
- Redux DevTools : voir les actions et l'état global
- Console : st.set avec debug: true
- snippet.javascript
const st = useStates({ initialStates: { ... }, debug: import.meta.env.DEV // Logs uniquement en dev });
Checklist avant déploiement
- [ ] Toutes les erreurs sont gérées
- [ ] Les chargements affichent un spinner
- [ ] La validation fonctionne côté client ET serveur
- [ ] Les droits sont vérifiés côté serveur
- [ ] Le mode offline fonctionne (si applicable)
- [ ] Les traductions sont complètes
- [ ] Les performances sont acceptables
- [ ] HTTPS est activé
- [ ] Les variables d'environnement de prod sont configurées
Ressources
- Documentation React : https://react.dev
- Documentation SmartCommon : https://inligit.fr/cap-rel/dolibarr/smartmaker/smartcommon
- Documentation Dolibarr : https://wiki.dolibarr.org