---
source_hash: "c5c31398"
title: "Chapter 2: State Management"
weight: 590
---

# Chapter 2: State Management

SmartCommon provides several hooks for state management according to needs:

- **useGlobalStates**: shared state between components with persistence
- **useStates**: local component state
- **useForm**: specialized state for forms

## useGlobalStates

Global state accessible everywhere, with automatic persistence.

### Basic Syntax

```javascript
import { useGlobalStates } from '@cap-rel/smartcommon';

function MyComponent() {
    const gst = useGlobalStates();

    return (
        <div>
            <p>User: {gst.get('session')?.user?.name}</p>
            <p>Language: {gst.get('settings')?.lng}</p>
        </div>
    );
}
```

### Persistence Configuration

In `appConfig.js`:

```javascript
export const config = {
    storage: {
        local: ["session", "settings"]  // Persisted in localStorage
    },

    globalState: {
        reducers: {
            session: null,
            settings: { lng: "fr", theme: "light" },
            cart: { items: [], total: 0 }
        }
    }
};
```

### Read and Write

```javascript
function SettingsPage() {
    const gst = useGlobalStates();
    const settings = gst.get('settings');

    const changeLanguage = (lng) => {
        gst.local.set('settings', { ...settings, lng });
    };

    const changeTheme = (theme) => {
        gst.local.set('settings', { ...settings, theme });
    };

    return (
        <div>
            <select
                value={settings?.lng}
                onChange={(e) => changeLanguage(e.target.value)}
            >
                <option value="fr">French</option>
                <option value="en">English</option>
            </select>

            <button onClick={() => changeTheme('light')}>Light</button>
            <button onClick={() => changeTheme('dark')}>Dark</button>
        </div>
    );
}
```

### Example: Shopping Cart

```javascript
function useCart() {
    const gst = useGlobalStates();
    const cart = gst.get('cart');

    const addItem = (product) => {
        const existing = cart.items.find(i => i.id === product.id);

        let newItems;
        if (existing) {
            newItems = cart.items.map(i =>
                i.id === product.id
                    ? { ...i, quantity: i.quantity + 1 }
                    : i
            );
        } else {
            newItems = [...cart.items, { ...product, quantity: 1 }];
        }

        const total = newItems.reduce(
            (sum, i) => sum + i.price * i.quantity,
            0
        );

        gst.set('cart', { items: newItems, total });
    };

    const removeItem = (productId) => {
        const newItems = cart.items.filter(i => i.id !== productId);
        const total = newItems.reduce(
            (sum, i) => sum + i.price * i.quantity,
            0
        );
        gst.set('cart', { items: newItems, total });
    };

    const clearCart = () => {
        gst.set('cart', { items: [], total: 0 });
    };

    return { cart, addItem, removeItem, clearCart };
}

// Usage
function ProductCard({ product }) {
    const { addItem } = useCart();

    return (
        <div>
            <h3>{product.label}</h3>
            <p>{product.price} €</p>
            <button onClick={() => addItem(product)}>
                Add to cart
            </button>
        </div>
    );
}

function CartIcon() {
    const { cart } = useCart();
    const itemCount = cart.items.reduce((sum, i) => sum + i.quantity, 0);

    return <span>🛒 {itemCount}</span>;
}
```

## useStates

Local state with path notation.

### Basic Syntax

```javascript
import { useStates } from '@cap-rel/smartcommon';

function MyComponent() {
    const st = useStates({
        initialStates: {
            count: 0,
            user: { name: '', email: '' },
            items: [],
            loading: false
        },
        debug: true  // Display changes in console
    });

    return (
        <div>
            <p>Count: {st.get('count')}</p>
            <button onClick={() => st.set('count', st.get('count') + 1)}>
                +1
            </button>
        </div>
    );
}
```

### Available Methods

| Method | Description |
| --- | --- |
| st.get(path) | Read a value |
| st.set(path, value) | Write a value |
| st.unset(path) | Delete a value |
| st.values | Object containing all values |

### Path notation

```javascript
const st = useStates({
    initialStates: {
        user: { name: '', address: { city: '' } },
        items: []
    }
});

// Read
st.get('user');                // { name: '', address: { city: '' } }
st.get('user.name');           // ''
st.get('user.address.city');   // ''
st.get('items');               // []
st.get('items[0]');            // undefined

// Write
st.set('user.name', 'John');
st.set('user.address.city', 'Paris');

// Write with function
st.set('count', prev => prev + 1);

// Array manipulation
st.set('items[]', { id: 1 });     // Push
st.set('items[0].name', 'test');  // Modify index
st.unset('items[0]');             // Delete index
```

### Example: Detail Page

```javascript
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Page, Block, Spinner } from '@cap-rel/smartcommon';
import { useApi, useStates } from '@cap-rel/smartcommon';

export const ProductDetailPage = () => {
    const { id } = useParams();
    const api = useApi();

    const st = useStates({
        initialStates: {
            product: null,
            loading: true,
            error: null,
            isEditing: false
        }
    });

    useEffect(() => {
        loadProduct();
    }, [id]);

    const loadProduct = async () => {
        st.set('loading', true);
        st.set('error', null);

        try {
            const data = await api.private.get(`products/${id}`).json();
            st.set('product', data);
        } catch (err) {
            st.set('error', err.message);
        } finally {
            st.set('loading', false);
        }
    };

    if (st.get('loading')) {
        return <Page><Spinner /></Page>;
    }

    if (st.get('error')) {
        return <Page><Block>Error: {st.get('error')}</Block></Page>;
    }

    const product = st.get('product');

    return (
        <Page title={product.label}>
            <Block>
                <p>Price: {product.price} €</p>
                <p>Stock: {product.stock}</p>
            </Block>
        </Page>
    );
};
```

## useForm

Specialized hook for forms with validation.

### Basic Syntax

```javascript
import { useForm } from '@cap-rel/smartcommon';
import { Form, Input, Button } from '@cap-rel/smartcommon';

function LoginForm() {
    const form = useForm({ defaultValues: { email: '', password: '' } });

    const handleSubmit = async (data) => {
        console.log('Validated data:', data);
    };

    return (
        <Form form={form} onSubmit={handleSubmit}>
            <Input name="email" label="Email" type="email" />
            <Input name="password" label="Password" type="password" />
            <Button type="submit" loading={form.isFormSubmitting}>
                Login
            </Button>
        </Form>
    );
}
```

### With Initial Values

```javascript
function EditProductForm({ product }) {
    const form = useForm({
        defaultValues: {
            label: product.label,
            price: product.price,
            description: product.description
        }
    });

    const handleSubmit = async (data) => {
        await api.private.put(`products/${product.id}`, { json: data });
    };

    return (
        <Form form={form} onSubmit={handleSubmit}>
            <Input name="label" label="Name" />
            <Input name="price" label="Price" type="number" />
            <Textarea name="description" label="Description" />
            <Button type="submit">Save</Button>
        </Form>
    );
}
```

### Manual Validation with setField

```javascript
// useForm does not perform automatic validation.
// Use setField to manage errors manually:

form.setField({
    name: 'email',
    value: inputValue,
    errors: {
        required: { condition: !inputValue },
        format: { condition: inputValue && !isValidEmail(inputValue) }
    }
});

// Check errors
const hasError = form.get('errors.email.required'); // true | false
```

## State Hooks Comparison

| Hook | Scope | Persistence | Use Case |
| --- | --- | --- | --- |
| useGlobalStates | Application | localStorage | Session, preferences, cart |
| useStates | Component | No | Page state, loading |
| useForm | Component | No | Forms with validation |
| useState (React) | Component | No | Simple state |

## Best Practices

### 1. Choose the Right Hook

```javascript
// User session -> useGlobalStates
const gst = useGlobalStates();
const session = gst.get('session');

// Page loading state -> useStates
const st = useStates({ initialStates: { loading: true, data: null } });

// Form -> useForm
const form = useForm({ defaultValues: { name: '', email: '' } });

// Simple toggle -> useState
const [isOpen, setIsOpen] = useState(false);
```

### 2. Organize Global State

```javascript
// appConfig.js
globalState: {
    reducers: {
        // Auth
        session: null,

        // User preferences
        settings: { lng: 'fr', theme: 'light' },

        // Global business data
        cart: { items: [], total: 0 },

        // Cache
        categories: []
    }
}
```

## useConfirm

Hook to display confirmation and alert modal dialogs.

### Import

```javascript
import { useConfirm } from '@cap-rel/smartcommon';
```

### Returned Functions

The hook returns an object with two functions:

```javascript
const { confirm, alert } = useConfirm();
```

- **confirm**: displays a dialog with Confirm/Cancel buttons, returns `true` or `false`
- **alert**: displays a dialog with a single OK button, always returns `true`

### Using confirm

```javascript
function DeleteButton({ item, onDelete }) {
    const { confirm } = useConfirm();

    const handleDelete = async () => {
        const confirmed = await confirm({
            type: 'delete',
            title: 'Delete this item?',
            message: `Are you sure you want to delete "${item.name}"?`,
            detail: item.ref,
            confirmText: 'Delete',
            cancelText: 'Cancel'
        });

        if (confirmed) {
            onDelete(item.id);
        }
    };

    return <button onClick={handleDelete}>Delete</button>;
}
```

### Using alert

```javascript
function SaveButton({ onSave }) {
    const { alert } = useConfirm();

    const handleSave = async () => {
        try {
            await onSave();
            await alert({
                type: 'info',
                title: 'Success',
                message: 'Data has been saved.'
            });
        } catch (err) {
            await alert({
                type: 'warning',
                title: 'Error',
                message: err.message
            });
        }
    };

    return <button onClick={handleSave}>Save</button>;
}
```

### Options

| Option | Type | Description |
| --- | --- | --- |
| type | string | Dialog type: `'danger'`, `'delete'`, `'warning'`, `'info'` |
| title | string | Dialog title |
| message | string | Confirmation message |
| detail | string | Additional text displayed in a gray box |
| confirmText | string | Confirm button text |
| cancelText | string | Cancel button text |

### Types and Icons

| Type | Icon | Button Color |
| --- | --- | --- |
| danger | Trash can (red) | Red |
| delete | Trash can (red) | Red |
| warning | Exclamation triangle (orange) | Orange |
| info | Info (blue) | Blue |
| (other) | Question mark (gray) | Blue (default) |

### Prerequisites

The `ConfirmProvider` component must wrap the application:

```javascript
import { ConfirmProvider } from '@cap-rel/smartcommon';

function App() {
    return (
        <ConfirmProvider labels={{ cancel: 'Cancel', confirm: 'OK' }}>
            <MyApp />
        </ConfirmProvider>
    );
}
```

The `labels` prop allows defining default button texts. If not provided, values are `"Cancel"` and `"OK"`.

## usePWAUpdate

Hook to detect and manage PWA application updates.

### Import

```javascript
import { usePWAUpdate } from '@cap-rel/smartcommon';
```

### Usage

```javascript
function UpdateBanner() {
    const {
        updateAvailable,
        updateActivated,
        checkForUpdates,
        applyUpdate
    } = usePWAUpdate({
        checkInterval: 300000  // Check every 5 min
    });

    if (!updateAvailable) return null;

    return (
        <div className="bg-blue-500 text-white p-4">
            <p>An update is available</p>
            <button onClick={applyUpdate}>
                Update now
            </button>
        </div>
    );
}
```

### With Automatic Reload

```javascript
function App() {
    usePWAUpdate({
        autoReload: true,
        onUpdateAvailable: () => {
            console.log('Update available');
        },
        onUpdateActivated: () => {
            console.log('Update activated');
        }
    });

    return <MyApp />;
}
```

### Options

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| autoReload | boolean | false | Automatically reload after update |
| checkInterval | number | 0 | Check interval in ms (0 = disabled) |
| onUpdateAvailable | function | - | Callback when update is available |
| onUpdateActivated | function | - | Callback when update is activated |

### Returned Values

| Property | Type | Description |
| --- | --- | --- |
| updateAvailable | boolean | Update pending |
| updateActivated | boolean | Update activated |
| registration | object | ServiceWorkerRegistration |
| checkForUpdates | function | Manually check |
| applyUpdate | function | Apply update |
| reloadPage | function | Reload page |

## UpdatePrompt

Ready-to-use UI component that encapsulates `usePWAUpdate` and displays a notification when an update is available.

### Import

```javascript
import { UpdatePrompt } from '@cap-rel/smartcommon';
```

### Display Variants

Three variants are available:

- **toast** (default): discrete notification via react-hot-toast
- **banner**: fixed banner at top or bottom of screen
- **modal**: centered modal dialog

### Direct Usage

```javascript
function App() {
    return (
        <div>
            <MyApp />
            <UpdatePrompt
                variant="banner"
                position="bottom"
                checkInterval={300000}
                labels={{
                    title: 'New version',
                    message: 'An update is available.',
                    reloadButton: 'Refresh',
                    dismissButton: 'Later'
                }}
            />
        </div>
    );
}
```

### Via Provider

The SmartCommon `Provider` accepts a `pwaUpdate` prop that automatically integrates `UpdatePrompt`:

```javascript
import { Provider } from '@cap-rel/smartcommon';
import { config } from './appConfig';

export const App = () => (
    <Provider
        config={config}
        pwaUpdate={{
            variant: 'toast',
            checkInterval: 300000
        }}
    >
        <Router />
    </Provider>
);
```

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| variant | string | `"toast"` | `"toast"`, `"banner"` or `"modal"` |
| position | string | `"bottom"` | Banner position: `"top"` or `"bottom"` |
| autoReload | boolean | `false` | Automatically reload after activation |
| checkInterval | number | `0` | Check interval in ms (0 = disabled) |
| labels | object | - | Custom texts (see below) |
| onUpdateAvailable | function | - | Callback when update is detected |
| onUpdateActivated | function | - | Callback when update is activated |

### Default Labels

| Key | Default Value |
| --- | --- |
| title | "Update available" |
| message | "A new version is available." |
| reloadButton | "Refresh" |
| dismissButton | "Later" |

## Key Points to Remember

1. **useGlobalStates** for shared and persisted data
2. **useStates** for local state with path notation
3. **useForm** for forms with manual validation
4. **useConfirm** for confirmation and alert dialogs
5. **usePWAUpdate** to detect PWA updates
6. **UpdatePrompt** to display a ready-to-use update UI
7. **Path notation**: `user.address.city`, `items[0]`, `items[]`
8. Configure **storage.local** for persistence

[Previous Chapter](/training/module7-smartcommon-hooks/useapi) | [Back to Module](/training/module7-smartcommon-hooks) | [Next Chapter: Utilities ->](/training/module7-smartcommon-hooks/utilitaires)
