Chapter 3: Utility Hooks

SmartCommon provides utility hooks for common tasks.

useNavigation

Programmatic navigation with React Router.

import { useNavigation } from '@cap-rel/smartcommon';

function MyComponent() {
    const nav = useNavigation();

    return (
        <div>
            {/* Navigate to route */}
            <button onClick={() => nav.navigate('/products')}>
                Products
            </button>

            {/* Navigate with parameter */}
            <button onClick={() => nav.navigate(`/products/${id}`)}>
                Details
            </button>

            {/* Go back */}
            <button onClick={() => nav.navigate(-1)}>
                Back
            </button>

            {/* Replace (no history) */}
            <button onClick={() => nav.replace('/login')}>
                Login
            </button>
        </div>
    );
}

useLibConfig

Access application configuration.

import { useLibConfig } from '@cap-rel/smartcommon';

function DebugInfo() {
    const config = useLibConfig();

    if (!config.debug) return null;

    return (
        <div className="debug-panel">
            <p>API URL: {config.api.prefixUrl}</p>
            <p>Mode: {config.debug ? 'Debug' : 'Production'}</p>
        </div>
    );
}

useIntl

Date and number formatting with the Intl API.

Date Formatting

import { useIntl } from '@cap-rel/smartcommon';

function EventCard({ event }) {
    const intl = useIntl();

    // Default format
    const dateFormatted = intl.DateTimeFormat(event.date);
    // "03/15/2024, 2:30:00 PM"

    // Custom format
    const dateOnly = intl.DateTimeFormat(event.date, 'fr-FR', {
        year: 'numeric',
        month: 'long',
        day: 'numeric'
    });
    // "15 mars 2024"

    // Time only
    const timeOnly = intl.DateTimeFormat(event.date, 'fr-FR', {
        hour: '2-digit',
        minute: '2-digit'
    });
    // "2:30 PM"

    return (
        <div>
            <h3>{event.title}</h3>
            <p>On {dateOnly} at {timeOnly}</p>
        </div>
    );
}

Number Formatting

function PriceDisplay({ price, currency = 'EUR' }) {
    const intl = useIntl();

    const formatted = intl.NumberFormat(price, 'fr-FR', {
        style: 'currency',
        currency: currency
    });
    // "29.99 €"

    return <span>{formatted}</span>;
}

function PercentDisplay({ value }) {
    const intl = useIntl();

    const formatted = intl.NumberFormat(value, 'fr-FR', {
        style: 'percent',
        minimumFractionDigits: 1
    });
    // "15.5%"

    return <span>{formatted}</span>;
}

useWindow

Browser window information.

import { useWindow } from '@cap-rel/smartcommon';

function ResponsiveLayout({ children }) {
    const { orientation, windowDimension, darkMode } = useWindow();

    // orientation: 'portrait' or 'landscape'
    // windowDimension: { w, h }
    // darkMode: boolean

    if (windowDimension.w < 768) {
        return <MobileLayout>{children}</MobileLayout>;
    }

    if (windowDimension.w < 1024) {
        return <TabletLayout>{children}</TabletLayout>;
    }

    return <DesktopLayout>{children}</DesktopLayout>;
}

function WindowInfo() {
    const { windowDimension } = useWindow();

    return <p>Window: {windowDimension.w} x {windowDimension.h}</p>;
}

useFile

File manipulation utilities.

Resize Image

import { useFile } from '@cap-rel/smartcommon';

function ImageUploader({ onUpload }) {
    const { resizeImage } = useFile();

    const handleFileChange = async (e) => {
        const file = e.target.files[0];
        if (!file) return;

        // Resize before upload
        const base64 = await resizeImage(file, {
            maxWidth: 1920,
            maxHeight: 1080,
            quality: 85
        });

        // base64 = "data:image/jpeg;base64,..."
        onUpload(base64);
    };

    return (
        <input
            type="file"
            accept="image/*"
            onChange={handleFileChange}
        />
    );
}

useDb

IndexedDB database via Dexie for local storage of large amounts of data.

Configuration

import { useDb } from '@cap-rel/smartcommon';

const db = useDb({
    name: 'myApp',
    version: 1,
    stores: {
        items: 'id++, name, category, createdAt',
        logs: 'id++, action, timestamp'
    },
    debug: true
});

CRUD Operations

function ItemsManager() {
    const db = useDb({
        name: 'myApp',
        version: 1,
        stores: {
            items: 'id++, name, category'
        }
    });

    // Create
    const addItem = async (item) => {
        const id = await db.items.add(item);
        console.log('Item created with id:', id);
        return id;
    };

    // Read all
    const getAllItems = async () => {
        return db.items.toArray();
    };

    // Read one
    const getItem = async (id) => {
        return db.items.get(id);
    };

    // Update
    const updateItem = async (id, changes) => {
        await db.items.update(id, changes);
    };

    // Delete
    const deleteItem = async (id) => {
        await db.items.delete(id);
    };

    // ...
}

Advanced Queries

// Filter by value
const electronics = await db.items
    .where('category')
    .equals('electronics')
    .toArray();

// Filter by range
const expensiveItems = await db.items
    .where('price')
    .above(100)
    .toArray();

// Sort
const sorted = await db.items
    .orderBy('createdAt')
    .reverse()
    .toArray();

// Limit
const first10 = await db.items
    .limit(10)
    .toArray();

// Count
const count = await db.items.count();

// Text search
const matching = await db.items
    .filter(item => item.name.includes('test'))
    .toArray();

Example: Offline Mode

import { useEffect } from 'react';
import { useApi, useDb, useStates } from '@cap-rel/smartcommon';

function OfflineCapableList() {
    const api = useApi();
    const db = useDb({
        name: 'myApp',
        version: 1,
        stores: { items: 'id, name, synced' }
    });

    const st = useStates({
        initialStates: { items: [], loading: true }
    });

    useEffect(() => {
        loadItems();
    }, []);

    const loadItems = async () => {
        try {
            // Try to load from API
            const data = await api.private.get('items').json();

            // Save locally
            await db.items.clear();
            await db.items.bulkAdd(data.map(i => ({ ...i, synced: true })));

            st.set('items', data);
        } catch (err) {
            // On network error, load from IndexedDB
            console.log('Offline mode - loading local');
            const localItems = await db.items.toArray();
            st.set('items', localItems);
        } finally {
            st.set('loading', false);
        }
    };

    const addItem = async (item) => {
        // Save locally first
        const id = await db.items.add({ ...item, synced: false });

        // Update UI
        st.set('items', [...st.get('items'), { ...item, id }]);

        // Try to sync
        try {
            const result = await api.private.post('items', { json: item }).json();
            await db.items.update(id, { ...result, synced: true });
        } catch (err) {
            console.log('Sync failed, will retry later');
        }
    };

    // ...
}

useAnimation

Animation management with Framer Motion.

import { useAnimation } from '@cap-rel/smartcommon';
import { motion } from 'framer-motion';

function AnimatedList({ items }) {
    const { start } = useAnimation();

    return (
        <div>
            {items.map((item, index) => (
                <motion.div
                    key={item.id}
                    initial={{ opacity: 0, y: 20 }}
                    animate={start ? { opacity: 1, y: 0 } : {}}
                    transition={{ delay: index * 0.1 }}
                >
                    {item.name}
                </motion.div>
            ))}
        </div>
    );
}

useListDnD

Drag and drop for reordering lists.

import { useListDnD } from '@cap-rel/smartcommon';

function SortableList({ initialItems, onReorder }) {
    const {
        items,
        onDragStart,
        onDragOver,
        onDrop
    } = useListDnD({
        initialItems,
        onReorder: (newItems) => {
            console.log('New order:', newItems);
            onReorder(newItems);
        }
    });

    return (
        <ul>
            {items.map((item, index) => (
                <li
                    key={item.id}
                    draggable
                    onDragStart={(e) => onDragStart(e, index)}
                    onDragOver={onDragOver}
                    onDrop={(e) => onDrop(e, index)}
                >
                    {item.name}
                </li>
            ))}
        </ul>
    );
}

Summary Table

Hook Usage Example
useNavigation Navigation nav.navigate('/products')
useLibConfig App config config.debug
useIntl Formatting intl.DateTimeFormat(date)
useWindow Responsive orientation, windowDimension, scroll, darkMode
useFile Files resizeImage(file, options)
useDb IndexedDB db.items.add(item)
useAnimation Animations start (boolean)
useListDnD Drag & drop onDragStart, onDragOver, onDrop

Key Points to Remember

  1. useNavigation for programmatic navigation
  2. useIntl for localized formatting
  3. useWindow for responsive design
  4. useFile for image processing
  5. useDb for local storage (offline)

Previous Chapter | Back to Module | Next Module: Backend API ->