---
title: "Module 11 : Bonnes pratiques"
weight: 740
description: "Ne pas faire confiance aux données du client :"
category: "Formation"
type: "formation"
---

# 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

```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

```javascript
// Pour les composants mémorisés
const handleClick = useCallback((id) => {
    setSelected(id);
}, []);
```

### Lazy loading des pages

```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

```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

```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

```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

```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

```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

```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 :

```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` :

```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

```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

```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

[← Chapitre précédent](/training/module10-fonctionnalites-avancees/autres) | [Retour à l'index de la formation](/training)
