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 notationuseDb: IndexedDB database via DexieuseCachedQuery: data cache with strategies (NetworkFirst, CacheFirst, SWR)
For complete offline synchronization, see Offline Synchronization (useSyncClient).
Documentation IndexedDB Documentation Dexie Documentation Redux Toolkit
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 timestampupdatedAt: last modification timestamplogstable: 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 - Complete hooks documentation
- API Requests - Combine with API calls