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('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

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