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
// É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
// Pour les composants mémorisés
const handleClick = useCallback((id) => {
setSelected(id);
}, []);
Lazy loading des pages
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() -> gst.get('session') |
| Préférences | useGlobalStates() -> gst.get('settings') |
| Données partagées | useGlobalStates() -> gst.get/set |
| Gros volumes, offline | useDb |
Structurer l'état global
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
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
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 manuelle avec setField
// useForm ne fait pas de validation automatique.
// Validez les champs manuellement :
form.setField({
name: 'email',
value: inputValue,
errors: {
required: { condition: !inputValue },
format: { condition: inputValue && !inputValue.includes('@') }
}
});
Affichage des erreurs
// Vérifier les erreurs
const hasError = form.get('errors.email.required');
const hasFormatError = form.get('errors.email.format');
Sécurité
Ne jamais stocker de secrets côté client
// 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 :
// 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 :
// 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
// 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
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