---
source_hash: "d2612d51"
title: "Module 11: Best Practices"
weight: 740
description: "Never trust client data:"
category: "Training"
type: "training"
---

# Module 11: Best Practices

> This final module sums up the best practices for developing SmartMaker applications.

## Code organization

### Component structure

```
components/pages/private/TasksPage/
├── index.jsx           # Main component (export)
├── TasksPage.jsx       # Logic and rendering
├── components/         # Local sub-components
│   ├── TaskList.jsx
│   └── TaskFilters.jsx
└── hooks/              # Hooks specific to this page
    └── useTasks.js
```

### Naming convention

- **Components**: PascalCase (`TaskList`, `UserProfile`)
- **Hooks**: camelCase with the use prefix (`useTasks`, `useAuth`)
- **Files**: same name as the component (`TaskList.jsx`)
- **Folders**: PascalCase for components, camelCase for hooks

## Performance

### Avoiding useless re-renders

```javascript
// Avoid: object recreated on every render
<Component style={{ color: 'red' }} />

// Prefer: stable object
const style = useMemo(() => ({ color: 'red' }), []);
<Component style={style} />
```

### Memoizing callbacks

```javascript
// For memoized components
const handleClick = useCallback((id) => {
    setSelected(id);
}, []);
```

### Lazy loading 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>
```

## State management

### Choosing the right tool

| Need | Solution |
| --- | --- |
| Form state | useStates or useForm |
| Page state (loading, error) | useStates |
| User session | useGlobalStates() -> gst.get('session') |
| Preferences | useGlobalStates() -> gst.get('settings') |
| Shared data | useGlobalStates() -> gst.get/set |
| Large volumes, offline | useDb |

### Structuring the global state

```javascript
globalState: {
    reducers: {
        // Auth
        session: null,

        // Preferences
        settings: { lng: 'fr', theme: 'light' },

        // Data cache
        cache: {
            categories: [],
            lastFetch: null
        }
    }
}
```

## API calls

### Always handle errors

```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 for debugging
        console.error('Load error:', err);
    } finally {
        st.set('loading', false);
    }
};
```

### Cancel requests on unmount

```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();
}, []);
```

## Forms

### Manual validation with setField

```javascript
// useForm does not validate automatically.
// Validate the fields manually:
form.setField({
    name: 'email',
    value: inputValue,
    errors: {
        required: { condition: !inputValue },
        format: { condition: inputValue && !inputValue.includes('@') }
    }
});
```

### Displaying errors

```javascript
// Check the errors
const hasError = form.get('errors.email.required');
const hasFormatError = form.get('errors.email.format');
```

## Security

### Never store secrets on the client side

```javascript
// NEVER
const API_KEY = 'secret123';

// OK: server environment variables only
// The client only has access to the VITE_ prefixed ones
const API_URL = import.meta.env.VITE_API_URL;
```

### Validate on the server side

Never trust client data:

```php
// PHP Controller
public function create($payload)
{
    // Always validate
    if (empty($payload['label'])) {
        return ['Label required', 400];
    }

    // Always check the permissions
    if (!$user->hasRight('mymodule', 'create')) {
        return ['Forbidden', 403];
    }

    // Always escape
    $label = $db->escape($payload['label']);
}
```

### Protecting against XSS

React escapes automatically, but beware of `dangerouslySetInnerHTML`:

```javascript
// Dangerous
<div dangerouslySetInnerHTML={{ __html: userContent }} />

// If needed, sanitize first
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />
```

## Tests

### Test the critical components

```javascript
// TasksPage.test.jsx
import { render, screen, waitFor } from '@testing-library/react';
import { TasksPage } from './TasksPage';

test('displays the task list', async () => {
    render(<TasksPage />);

    await waitFor(() => {
        expect(screen.getByText('My tasks')).toBeInTheDocument();
    });
});
```

## Debug

### Use the tools

- **React DevTools**: inspect the components and the state
- **Redux DevTools**: see the actions and the global state
- **Console**: st.set with debug: true

```javascript
const st = useStates({
    initialStates: { ... },
    debug: import.meta.env.DEV  // Logs in dev only
});
```

## Pre-deployment checklist

- [ ] All errors are handled
- [ ] Loading states show a spinner
- [ ] Validation works on the client AND the server
- [ ] Permissions are checked on the server side
- [ ] Offline mode works (if applicable)
- [ ] Translations are complete
- [ ] Performance is acceptable
- [ ] HTTPS is enabled
- [ ] Production environment variables are configured

## Resources

- React documentation: https://react.dev
- SmartCommon documentation: https://inligit.fr/cap-rel/dolibarr/smartmaker/smartcommon
- Dolibarr documentation: https://wiki.dolibarr.org

[Previous Chapter](/training/module10-fonctionnalites-avancees/autres) | [Back to the training index](/training)
