Indice

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

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() → 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

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 manuelle avec setField

snippet.javascript
// 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

snippet.javascript
// 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

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

snippet.javascript
const st = useStates({
    initialStates: { ... },
    debug: import.meta.env.DEV  // Logs uniquement en dev
});

Checklist avant déploiement

Ressources

← Chapitre précédent | Retour à l'index de la formation