Skip to content
⚠️ Beta: API may change before v1.0. Pin to ~0.x.0 to avoid breaking changes.

Storage Stores

Built-in storage implementations for persisting flow state across different environments.

import { createLocalStorageStore } from "@useflow/react";
const store = createLocalStorageStore(localStorage, {
prefix: "flow" // Optional: custom key prefix
});

Characteristics:

  • Persists across browser sessions
  • ~5-10MB storage limit
  • Synchronous API
  • Same-origin only
import { createMemoryStore } from "@useflow/react";
const store = createMemoryStore();

Characteristics:

  • In-memory only (no persistence)
  • Perfect for testing
  • No storage limits
  • Synchronous API
import AsyncStorage from "@react-native-async-storage/async-storage";
import { createAsyncStorageStore } from "@useflow/react-native";
const store = createAsyncStorageStore(AsyncStorage, {
prefix: "flow" // Optional: custom key prefix
});

Characteristics:

  • Persists on device
  • ~6MB limit on Android, larger on iOS
  • Asynchronous API
  • App-specific storage

All stores implement this interface:

interface StorageStore {
get(key: string): Promise<string | null> | string | null;
set(key: string, value: string): Promise<void> | void;
delete(key: string): Promise<void> | void;
clear?(): Promise<void> | void;
}
import {
createLocalStorageStore,
createPersister
} from "@useflow/react";
// Create store
const store = createLocalStorageStore();
// Create persister with store
const persister = createPersister({ store });
// Use with Flow
<Flow flow={myFlow} persister={persister}>
{({ renderStep }) => renderStep({ /* ... */ })}
</Flow>
const store = createLocalStorageStore(localStorage, {
prefix: "myapp"
});
// Keys will be: "myapp:flowId:variantId:instanceId"
import { createMemoryStore, createPersister } from "@useflow/react";
describe('MyFlow', () => {
it('should persist state', () => {
const store = createMemoryStore();
const persister = createPersister({ store });
render(
<Flow flow={myFlow} persister={persister}>
{/* ... */}
</Flow>
);
// State is saved in memory during test
});
});
const store = process.env.NODE_ENV === 'test'
? createMemoryStore()
: createLocalStorageStore();
const persister = createPersister({ store });
<Flow
flow={myFlow}
persister={persister}
onPersistenceError={(error) => {
if (error.name === 'QuotaExceededError') {
// Storage full - clear old data
localStorage.clear();
}
}}
>
{/* ... */}
</Flow>
// Check localStorage usage
const getStorageSize = () => {
let size = 0;
for (const key in localStorage) {
if (key.startsWith('flow:')) {
size += localStorage[key].length;
}
}
return size;
};
console.log(`Using ${getStorageSize()} bytes`);

Create your own store by implementing the interface:

import Redis from 'ioredis';
function createRedisStore(redis: Redis): StorageStore {
return {
async get(key: string) {
return await redis.get(key);
},
async set(key: string, value: string) {
await redis.set(key, value);
},
async delete(key: string) {
await redis.del(key);
},
async clear() {
const keys = await redis.keys('flow:*');
if (keys.length > 0) {
await redis.del(...keys);
}
}
};
}
const redis = new Redis();
const store = createRedisStore(redis);
const persister = createPersister({ store });
function createDatabaseStore(db: Database): StorageStore {
return {
async get(key: string) {
const row = await db.query(
'SELECT value FROM flow_states WHERE key = ?',
[key]
);
return row?.[0]?.value || null;
},
async set(key: string, value: string) {
await db.query(
'INSERT OR REPLACE INTO flow_states (key, value) VALUES (?, ?)',
[key, value]
);
},
async delete(key: string) {
await db.query(
'DELETE FROM flow_states WHERE key = ?',
[key]
);
}
};
}
StoreUse WhenAvoid When
localStorageLong-term persistence neededLarge data, private data
sessionStorageTab-specific stateNeed cross-tab state
MemoryTesting, temporary stateProduction persistence
AsyncStorageReact Native appsWeb applications
CustomSpecial requirementsSimple use cases
import { encrypt, decrypt } from 'your-crypto-lib';
const encryptedStore: StorageStore = {
async get(key) {
const encrypted = localStorage.getItem(key);
if (!encrypted) return null;
return decrypt(encrypted);
},
async set(key, value) {
const encrypted = encrypt(value);
localStorage.setItem(key, encrypted);
},
async delete(key) {
localStorage.removeItem(key);
}
};
const persister = createPersister({
store: encryptedStore
});
<Flow
flow={myFlow}
persister={persister}
saveMode="navigation" // Only save on navigation, not every change
saveDebounce={500} // Debounce saves by 500ms
>
// Remove old flow states periodically
const cleanupOldStates = () => {
const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
for (const key in localStorage) {
if (key.startsWith('flow:')) {
try {
const data = JSON.parse(localStorage.getItem(key));
if (data.savedAt < oneWeekAgo) {
localStorage.removeItem(key);
}
} catch {}
}
}
};
import { compress, decompress } from 'lz-string';
const compressedStore: StorageStore = {
get(key) {
const compressed = localStorage.getItem(key);
return compressed ? decompress(compressed) : null;
},
set(key, value) {
localStorage.setItem(key, compress(value));
},
delete(key) {
localStorage.removeItem(key);
}
};