---
source_hash: "3871c08a"
title: "Data Storage"
weight: 180
---

# Data Storage

SmartCommon offers several solutions for client-side data storage:
- `useGlobalStates`: global state with automatic persistence (localStorage/sessionStorage)
- `useStates`: reactive local state with path notation
- `useDb`: IndexedDB database via Dexie
- `useCachedQuery`: data cache with strategies (NetworkFirst, CacheFirst, SWR)

For complete offline synchronization, see [Offline Synchronization](/front/synchronisation) (`useSyncClient`).

Documentation [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
Documentation [Dexie](https://dexie.org/)
Documentation [Redux Toolkit](https://redux-toolkit.js.org/)

## useGlobalStates (recommended)

The `useGlobalStates` hook provides a global state with automatic persistence. This is the recommended method for user data (session, preferences, etc.).

### Basic Usage

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

export const MyComponent = () => {
  const gst = useGlobalStates();

  // Read a value
  const user = gst.get('user');

  // Write to localStorage (persistent)
  const login = (userData) => {
    gst.local.set('user', userData);
  };

  // Write to sessionStorage (session only)
  const setTempData = (data) => {
    gst.session.set('tempData', data);
  };

  // Delete a value
  const logout = () => {
    gst.unset('user');
  };

  return (
    <div>
      {user ? `Hello ${user.name}` : 'Not connected'}
    </div>
  );
};
```

### Available Methods

| Method | Description |
| --- | --- |
| `gst.get(path)` | Retrieve a value by its path |
| `gst.local.set(path, value)` | Store in localStorage (persistent) |
| `gst.session.set(path, value)` | Store in sessionStorage (session) |
| `gst.unset(path)` | Remove a value |
| `gst.values` | Object containing all values |

### Path Notation

You can access nested data using dot notation:

```
// Set nested data
gst.local.set('user.preferences.theme', 'dark');
gst.local.set('user.preferences.language', 'en');

// Read nested data
const theme = gst.get('user.preferences.theme'); // 'dark'
const prefs = gst.get('user.preferences'); // { theme: 'dark', language: 'en' }
```

## useStates (local state)

The `useStates` hook provides a reactive local state with the same API as `useGlobalStates`.

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

export const MyForm = () => {
  const st = useStates({
    initialStates: {
      name: '',
      email: '',
      errors: {}
    },
    debug: true // displays changes in the console
  });

  const handleChange = (field, value) => {
    st.set(field, value);
  };

  const validate = () => {
    if (!st.get('name')) {
      st.set('errors.name', 'Name is required');
    }
  };

  return (
    <form>
      <Input
        value={st.get('name')}
        onChange={(e) => handleChange('name', e.target.value)}
        error={st.get('errors.name')}
      />
    </form>
  );
};
```

### Available Methods

| Method | Description |
| --- | --- |
| `st.get(path)` | Retrieve a value |
| `st.set(path, value)` | Set a value |
| `st.unset(path)` | Remove a value |
| `st.values` | Object containing all values |
| `st.states` | Alias of values |

### Array Manipulation

```
const st = useStates({ initialStates: { items: [] } });

// Add an element (push)
st.set('items[]', { id: 1, name: 'Item 1' });

// Modify an element by index
st.set('items[0].name', 'Modified item');

// Remove an element by index
st.unset('items[0]');
```

## useCachedQuery (cache with strategies)

The `useCachedQuery` hook allows caching API data in IndexedDB with configurable strategies.

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

const ItemsList = () => {
  const db = useMemo(() => new Dexie('myApp'), []);

  const {
    data,          // data (from cache or network)
    isLoading,     // loading in progress
    isFromCache,   // true if data comes from cache
    isStale,       // true if data is stale
    error,         // possible error
    lastFetch,     // timestamp of last fetch
    refetch,       // force a new fetch
    invalidate,    // invalidate cache
  } = useCachedQuery({
    db,
    store: 'cache',         // IndexedDB store name (schema: 'key')
    key: 'items-list',      // unique cache key
    fetchFn: () => api.get('items'),  // fetch function
    strategy: 'networkFirst',         // cache strategy
    ttl: 3600000,           // cache TTL (1h default)
    staleTime: 60000,       // time before cache is "stale" (1min default)
    enabled: true,          // enable/disable fetch
  });

  if (isLoading) return <Spinner />;

  return (
    <List>
      {data?.map(item => (
        <ListItem key={item.id}>{item.label}</ListItem>
      ))}
    </List>
  );
};
```

### Available Strategies

| Strategy | Constant | Behavior |
| --- | --- | --- |
| `networkFirst` | `NETWORK_FIRST` | Network first, cache as fallback (default) |
| `cacheFirst` | `CACHE_FIRST` | Cache first, network if absent or expired |
| `swr` | `STALE_WHILE_REVALIDATE` | Returns cache immediately, revalidates in background |

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

// Usage with constant
useCachedQuery({
  // ...
  strategy: CACHE_STRATEGIES.STALE_WHILE_REVALIDATE,
});
```

## useDb (IndexedDB)

For storing large amounts of data or complex structured data, use `useDb` based on Dexie.

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

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

export const ItemsManager = () => {
  const [items, setItems] = useState([]);

  // Load items
  useEffect(() => {
    db.items.toArray().then(setItems);
  }, []);

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

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

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

  // Query with filter
  const getByCategory = async (category) => {
    return db.items.where('category').equals(category).toArray();
  };

  return (/* ... */);
};
```

### Automatic Features

`useDb` automatically adds:
- `createdAt`: creation timestamp
- `updatedAt`: last modification timestamp
- `logs` table: journal of all operations (create, update, delete)

### Dexie Queries

```
// All items
const all = await db.items.toArray();

// By primary key
const item = await db.items.get(1);

// Filter
const filtered = await db.items
  .where('category')
  .equals('electronics')
  .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();
```

> [!TIP]
> For API cache with automatic strategies, prefer `useCachedQuery`. For complete offline synchronization with conflict management, use `useSyncClient`.

## Redux (classic method)

If you prefer to use Redux directly, SmartCommon provides `ReduxProvider`.

### Create a Slice

```
// src/redux/reducers/sessionSlice.js

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  data: JSON.parse(sessionStorage.getItem("session")) ?? null
};

const sessionSlice = createSlice({
  name: "session",
  initialState,
  reducers: {
    setSession(state, action) {
      state.data = action.payload;
      sessionStorage.setItem("session", JSON.stringify(action.payload));
    },
    unsetSession(state) {
      state.data = null;
      sessionStorage.removeItem("session");
    }
  },
});

export default sessionSlice.reducer;
export const { setSession, unsetSession } = sessionSlice.actions;
```

### Configure the Store

```
// src/redux/index.js

import sessionReducer from "./reducers/sessionSlice";
import { combineReducers } from "redux";
import { configureStore } from "@reduxjs/toolkit";

const rootReducer = combineReducers({
  session: sessionReducer
});

export const reduxStore = configureStore({ reducer: rootReducer });
export * from "./reducers/sessionSlice";
```

### Use in Components

```
import { useSelector, useDispatch } from "react-redux";
import { setSession, unsetSession } from "../../../redux";

export const MyComponent = () => {
  const session = useSelector(state => state.session.data);
  const dispatch = useDispatch();

  const login = (data) => dispatch(setSession(data));
  const logout = () => dispatch(unsetSession());

  return (/* ... */);
};
```

## Method Comparison

| Method | Use Case | Persistence |
| --- | --- | --- |
| `useGlobalStates` | User session, preferences | localStorage / sessionStorage |
| `useStates` | Form state, local UI | Memory (non-persistent) |
| `useDb` | Structured data, local storage | IndexedDB |
| `useCachedQuery` | API cache with strategies | IndexedDB |
| `useSyncClient` | Complete offline synchronization | IndexedDB |
| Redux | Complex applications, middleware | Configurable |

> [!TIP]
> For most cases, `useGlobalStates` and `useStates` are sufficient. For simple IndexedDB storage, use `useDb`. For API data caching, use `useCachedQuery`. For complete offline mode with synchronization, use `useSyncClient`.

## See Also
- [Hooks](/front/hooks) - Complete hooks documentation
- [API Requests](/front/requetes-api) - Combine with API calls
