SmartCommon Hooks

SmartCommon provides a set of React hooks to facilitate application development.

Global Hooks

These hooks use the application context and must be used within the Provider.

useApi

API call management with automatic JWT authentication.

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

const api = useApi();

// Login
await api.login({ login, password, rememberMe: true });

// Logout
await api.logout();

// Authenticated request
const data = await api.private.get('items').json();

// Public request
const info = await api.public.get('public/info').json();

See API Requests for more details.

useGlobalStates

Global state with automatic persistence (localStorage/sessionStorage).

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

const gst = useGlobalStates();

// Read
const user = gst.get('user');
const theme = gst.get('settings.theme');

// Write (persistent)
gst.local.set('user', userData);

// Write (session only)
gst.session.set('tempData', data);

// Delete
gst.unset('user');

// Direct access to values
const { user, settings } = gst.values;

See Data Storage for more details.

useNavigation

Navigation utilities for react-router-dom.

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

const nav = useNavigation();

// Programmatic navigation
nav.navigate('/dashboard');
nav.navigate(-1); // Go back

// Route information
const { pathname, search, hash } = nav.location;

// URL parameters
const { id } = nav.params;

useLibConfig

Access to application configuration.

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

const config = useLibConfig();

console.log(config.debug);     // true/false
console.log(config.api);       // { prefixUrl, timeout, ... }

useConfirm

Confirmation and alert dialogs (requires ConfirmProvider).

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

const { confirm, alert } = useConfirm();

// Confirmation (returns true/false)
const handleDelete = async () => {
  const ok = await confirm({
    type: 'delete',        // 'danger' | 'delete' | 'warning' | 'info'
    title: 'Delete?',
    message: 'This action cannot be undone.',
    detail: item.label,    // optional detail
    confirmText: 'Delete',
    cancelText: 'Cancel',
  });

  if (ok) {
    await api.del(`items/${item.id}`);
  }
};

// Alert (single OK button)
await alert({
  type: 'info',
  title: 'Information',
  message: 'Operation completed successfully.',
});

usePWAUpdate

PWA update management via Service Worker.

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

const { updateAvailable, checkForUpdates, applyUpdate } = usePWAUpdate({
  autoReload: false,
  checkInterval: 60000,
});

See PWA for more details.

Local Hooks

These hooks manage component-local state.

useStates

Local state management with path notation.

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

const st = useStates({
  initialStates: {
    count: 0,
    user: { name: '', email: '' },
    items: []
  },
  debug: true
});

// Read
st.get('count');           // 0
st.get('user.name');       // ''
st.get('items[0]');        // undefined

// Write
st.set('count', 1);
st.set('user.name', 'John');
st.set('items[]', { id: 1 });  // Push to array

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

// Delete
st.unset('user.email');
st.unset('items[0]');

// Direct access
const { count, user, items } = st.values;

useForm

Form management with state and errors.

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

const form = useForm({
  defaultValues: {
    name: '',
    email: ''
  },
  debug: true
});

// Read values
const name = form.get('values.name');

// Set a field with validation
form.setField({
  name: 'email',
  value: 'test@example.com',
  errors: {
    required: { condition: !value },
    format: { condition: !isValidEmail(value) }
  }
});

// Check errors
const hasEmailError = form.get('errors.email.required');

// Direct access
const { values, errors, isFormSubmitting, isFormSubmitted } = form;

useDb

IndexedDB database via Dexie with automatic logging.

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

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

// CRUD via Dexie
await db.items.add({ name: 'Item 1' });
const all = await db.items.toArray();
await db.items.update(id, { name: 'Updated' });
await db.items.delete(id);

Automatically adds createdAt, updatedAt and a logs table.

See Data Storage for more details.

Tip

For API caching with strategies, prefer useCachedQuery. For offline synchronization, use useSyncClient.

useCachedQuery

Query caching with network/cache strategies. This is the hook to use for data read from API and frequently re-read: dictionaries, configuration, reference lists.

Important

The exact name is useCachedQuery, with the d. useCacheQuery does not exist.

Declare the Cache Store

The hook stores its entries in a Dexie store that you must declare, indexed on key:

// src/db/index.js
export const db = new Db({
  name: "myapp",
  version: 1,
  stores: {
    queryCache: 'key',
    // ... your other stores
  },
}).db;

Usage

import { useCachedQuery, CACHE_STRATEGIES } from '@cap-rel/smartcommon';
import { db } from 'src/db';

const {
  data,         // data, from network or cache
  isLoading,    // true while fetching
  isFromCache,  // true if data comes from cache
  isStale,      // true if cached data is stale
  error,        // possible error
  lastFetch,    // timestamp of last successful fetch
  refetch,      // retry the request
  invalidate,   // clear cache and retry
} = useCachedQuery({
  db: db.instance,             // Dexie instance (db.instance, not db)
  store: 'queryCache',         // store name declared above
  key: 'countries',            // cache key, unique per request
  fetchFn: () => api.private.get('dictionaries/countries').json(),
  strategy: CACHE_STRATEGIES.CACHE_FIRST,
  ttl: 86400000,               // cache TTL, default 1h
  staleTime: 60000,            // stale threshold, default 1min
  enabled: true,               // disable request if false
});

Choose the Strategy

Strategy Behavior When to Use
NETWORK_FIRST (network-first) network first, cache as fallback if request fails default; business data that must be fresh but must remain readable offline
CACHE_FIRST (cache-first) cache if still valid, network otherwise dictionaries and references that rarely change (countries, units, types)
STALE_WHILE_REVALIDATE (swr) display cache immediately, revalidate in background configuration, preferences: display is instant and self-corrects

ttl and staleTime, Do Not Confuse

Option Default Effect
ttl 1 h beyond this, entry is deleted and considered absent
staleTime 1 min beyond this, entry is still served but isStale becomes true

So staleTime is always less than ttl. A dictionary type is set with a ttl of 24 hours.

Invalidate

// After a write that makes cache obsolete
await api.private.post('items', { json: payload });
await invalidate();     // clear entry and retry request

refetch() retries the request without clearing cache: the cached value remains available if network fails.

Offline Behavior

The hook relies on useOnlineStatus. Offline, it serves cache if it exists and does not emit any request. Test isFromCache to notify the user they are consulting locally stored data.

{isFromCache && isStale && (
  <span className="text-xs italic">Data saved on {formatDate(lastFetch)}</span>
)}

Tip

useCachedQuery does not replace the business database. A list of objects that the user creates, modifies or deletes offline falls under Db and useSyncClient. useCachedQuery is designed for read data, not for produced data.

See Data Storage and Offline Synchronization.

useOnlineStatus

Network connection detection with optional health check.

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

const { isOnline, isServerReachable, lastOnline, checkNow } = useOnlineStatus({
  healthCheckUrl: '/api/health',
  healthCheckInterval: 30000,
  stabilityDelay: 2000,
  timeout: 5000,
});

useAuthenticatedImage

Authenticated image loading with IndexedDB cache.

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

const { src, isLoading, isFromCache, error } = useAuthenticatedImage({
  db: db.instance,              // Dexie instance
  url: `/api/users/${id}/photo`,
  token: accessToken,
  ttl: 86400000,                // cache duration: 24h
  staleTime: 3600000,           // stale after 1h (background refresh)
  placeholder: '/images/default.png',
});

return <img src={src} alt="Photo" />;

Utility Hooks

useIntl

Date and number formatting with Intl API.

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

const intl = useIntl();

// Format a date
const formatted = intl.DateTimeFormat(Date.now());
// "01/11/2025, 14:30:00"

// With custom options
const dateOnly = intl.DateTimeFormat(Date.now(), 'fr-FR', {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});
// "11 janvier 2025"

useAnimation

Animation management with Framer Motion.

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

const { start, animations, setAnimations } = useAnimation({
  fadeIn: { value: false, state: null },
  slideIn: { value: false, state: null }
});

// start becomes true after first render
// Use it to trigger entry animations

useEffect(() => {
  if (start) {
    setAnimations(prev => ({
      ...prev,
      fadeIn: { ...prev.fadeIn, state: 'visible' }
    }));
  }
}, [start]);

useFile

File management utilities.

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

const { resizeImage } = useFile();

// Resize an image
const handleFileChange = async (e) => {
  const file = e.target.files[0];

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

  console.log(base64); // data:image/jpeg;base64,...
};

useVariantMerger

Props merging with component variants (mainly internal use).

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

const MyComponent = (props) => {
  const { variantProps, mergeProps, mergeQuickProps, setParams } = useVariantMerger('MyComponent', props);

  return (
    <div {...mergeProps('container', p => ({
      ...p,
      className: `base-class ${p.className || ''}`
    }))}>
      {variantProps.children}
    </div>
  );
};
Property Type Description
variantProps object Props merged from global theme + variant + props
mergeProps function mergeProps(key, fn): merge props of an element/sub-component
mergeQuickProps function mergeQuickProps(props, keys): extract a subset of variantProps
setParams function setParams(params): set dynamic parameters for className functions

useListDnD

Drag and drop for lists (reordering). Takes the set function from useStates as parameter.

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

const st = useStates({
  initialStates: {
    items: [{ id: 1, label: 'A' }, { id: 2, label: 'B' }],
    dragIndex: null,
  }
});

const { onDragStart, onDragOver, onDrop } = useListDnD(st.set);

// Usage on each list element
{st.get('items').map((item, index) => (
  <div
    key={item.id}
    draggable
    onDragStart={(e) => onDragStart(e, 'dragIndex', 'items')}
    onDragOver={(e) => onDragOver(e, 'dragIndex', index, 'items', st.get('items'))}
    onDrop={() => onDrop('dragIndex')}
  >
    {item.label}
  </div>
))}

Callback parameters:

Callback Parameters Description
onDragStart (e, indexLabel, parent) indexLabel: state key for dragged index
onDragOver (e, indexLabel, index, listLabel, list) Reorder list in real time
onDrop (indexLabel) Reset index to null

useCalculator

Programmatic control of the Calculator component (requires a Calculator in the component tree).

import { useCalculator, Calculator } from '@cap-rel/smartcommon';

const MyComponent = () => {
  const { isOpen, open, close, toggle } = useCalculator();

  return (
    <>
      <button onClick={() => open((result) => console.log('Result:', result))}>
        Open calculator
      </button>
      <Calculator />
    </>
  );
};
Property Type Description
isOpen boolean Calculator open state
open function open(onResult?): open calculator with optional callback
close function Close calculator
toggle function Toggle open/close

useWindow

Browser window information.

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

const { orientation, windowDimension, scroll, darkMode } = useWindow();

// Dimensions
const { w, h } = windowDimension;

// Orientation: 'landscape' | 'portrait'
console.log(orientation);

// Scroll position
const { x, y } = scroll;

// System dark mode: true | false
console.log(darkMode);

useIsDesktop

Responsive desktop/mobile detection.

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

const isDesktop = useIsDesktop();

if (isDesktop) {
  return <DesktopLayout />;
}
return <MobileLayout />;

useStatesWorking

Simplified local state management with nested paths.

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

const { states, set, get, unset } = useStatesWorking({
  count: 0,
  user: { name: '', email: '' }
});

// Read
get('user.name');

// Write (path notation)
set('user.name', 'John');
set('items[0]', { id: 1 });
set('items.[]', newItem);  // Push to array

// Delete
unset('user.email');

Tip

Similar to useStates but with a more direct API: initial values are passed directly, without initialStates wrapper.

useSyncClient

Offline/online synchronization with conflict management.

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

const sync = useSyncClient({
  apiUrl: import.meta.env.VITE_API_URL,
  getAccessToken: () => api.accessToken,
  scope: ['items', 'categories']
});

// Connection state
const { isOnline, isServerReachable } = sync;

// Sync state
const { isInitialized, isSyncing, pendingCount, conflictsCount, lastSyncTime, syncError } = sync;

// Local CRUD operations (work offline)
await sync.create('items', { ref: 'IT-001', label: 'Item 1' });
await sync.update('items', id, { label: 'Updated item' });
await sync.remove('items', id);
await sync.upsert('items', { id, label: 'Create or update' });

// Local read
await sync.getEntity('items', id);
const items = await sync.queryEntities('items', { category: 'A' });

// Manual synchronization
await sync.sync();      // Push + Pull
await sync.push();      // Send local changes
await sync.pull();      // Fetch server changes

// Conflict management
const conflicts = await sync.getConflicts();
await sync.resolveConflict(conflictId, 'client'); // 'client' | 'server' | mergedData

// Device registration
await sync.register({ deviceName: 'My phone' });

// Reset
await sync.reset();

See Synchronization for more details.

Summary Table

Hook Category Description
useApi Global API calls with JWT auth
useGlobalStates Global Persistent global state
useNavigation Global react-router navigation
useLibConfig Global App configuration
useConfirm Global Confirmation dialogs
usePWAUpdate Global PWA updates
useStates Local Local state with path notation
useStatesWorking Local Simplified local state with paths
useForm Local Form management
useCachedQuery Local Data caching with strategies
useOnlineStatus Local Network connection detection
useAuthenticatedImage Local Authenticated images with cache
useSyncClient Sync Offline/online synchronization
useIntl Utility Date/number formatting
useAnimation Utility Framer Motion animations
useFile Utility File manipulation
useVariantMerger Utility Variant merging
useCalculator Utility Calculator component control
useListDnD Utility Drag and drop
useWindow Utility Window/orientation/scroll info
useIsDesktop Utility Desktop/mobile detection

See Also