Persistent State

Zero-setup persistent state for miniapps. The SDK handles everything automatically.

Overview

The SDK provides a complete persistent state solution with no external dependencies:

  • Vanilla store – No Zustand/Redux required
  • AsyncStorage backend – Built-in local persistence
  • Automatic lifecycle – Saves on app background, no hook needed
  • Debounced saves – Coalesces rapid mutations (500ms)
  • Miniapp isolation – Each miniapp's state is namespaced
  • Cloud sync hooks – Optional callbacks to sync with your cloud backend

Using the hook

Inside miniapp components, use useNamedPersistentState for data that should survive app restarts:

TSX
import { useNamedPersistentState } from '@dpage-ai/react-native-dpage';

function Counter() {
  const [count, setCount] = useNamedPersistentState('count', 0);
  
  return (
    <Button onPress={() => setCount(c => c + 1)}>
      Count: {count}
    </Button>
  );
}

When to use it

PropTypeDefaultDescription
useNamedPersistentStatePersistent-Data that must survive app restarts (progress, settings, drafts)
useStateEphemeral-Temporary UI state (modals, spinners, in-flight input)

Keys should be descriptive: e.g. counter.value, settings.theme, user.profile.

Lifecycle flow

  1. App opens – Call initLifecycleListener() once at startup
  2. Miniapp screen opens restoreMiniapp(appId) loads persisted state
  3. State mutations – Debounced saves (500ms) reduce redundant writes
  4. App backgrounds – Automatic save of all active miniapp states

App integration

For apps using the SDK, the setup is minimal:

TSX
// MiniappViewer.tsx
import { 
  MiniappStateProvider, 
  PersistenceManager 
} from '@dpage-ai/react-native-dpage';

function MiniappViewer({ appId }) {
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    PersistenceManager.restoreMiniapp(appId)
      .finally(() => setIsReady(true));
  }, [appId]);

  if (!isReady) return <Loading />;

  return (
    <MiniappStateProvider miniappId={appId}>
      <MiniappComponent miniappId={appId} />
    </MiniappStateProvider>
  );
}

Enable Auto-Save

Call initLifecycleListener() once at app startup to enable automatic saves when the app goes to background.

Cloud Sync

AsyncStorage is always the local source of truth. The SDK includes a built-in CloudSync client for syncing to DPage's cloud. State is synced via an edge-first architecture:

  1. Edge (Cloudflare KV) – Fast reads/writes at the edge
  2. PostgreSQL (Supabase) – Async durability sync for backup

This gives you sub-50ms latency for state operations.

TSX
import { 
  PersistenceManager, 
  CloudSync 
} from '@dpage-ai/react-native-dpage';

// Use the built-in DPage cloud sync
PersistenceManager.setSyncCallbacks({
  // Called when a miniapp closes (user navigates back)
  onMiniappClose: async (miniappId) => {
    const snapshot = await PersistenceManager.getSnapshot(miniappId);
    if (snapshot) {
      const result = await CloudSync.upload(miniappId, snapshot);
      if (result.success) {
        await PersistenceManager.markSynced(miniappId);
      }
    }
  },

  onAppForeground: async () => {
    const unsynced = await PersistenceManager.getUnsyncedMiniapps();
    for (const id of unsynced) {
      const snapshot = await PersistenceManager.getSnapshot(id);
      if (snapshot) {
        await CloudSync.upload(id, snapshot);
        await PersistenceManager.markSynced(id);
      }
    }
  },

  onAppBackground: async () => {
    // Batch upload all unsynced states
    const unsynced = await PersistenceManager.getUnsyncedMiniapps();
    const states = await Promise.all(
      unsynced.map(async (id) => ({
        miniappId: id,
        snapshot: await PersistenceManager.getSnapshot(id),
      }))
    );
    await CloudSync.uploadBatch(states.filter(s => s.snapshot));
    await PersistenceManager.markAllSynced();
  },
});

CloudSync API

CloudSync.upload(), CloudSync.download(), CloudSync.uploadBatch(), CloudSync.downloadBatch() – use your auth from setUserAuth({userId, authToken}). Enable cloud sync with cloudSync: true in initDPage().

Sync Status

The SDK tracks sync status automatically:

TSX
// Check sync status
const status = await PersistenceManager.getSyncStatus(miniappId);
// { miniappId, lastLocalSave, lastCloudSync, needsSync }

// Get unsynced miniapps
const unsynced = await PersistenceManager.getUnsyncedMiniapps();

// Mark as synced after cloud upload
await PersistenceManager.markSynced(miniappId);

// Hydrate from cloud (updates both memory and AsyncStorage)
await PersistenceManager.hydrateFromCloud(miniappId, cloudSnapshot);

Storage Structure

All data is stored in AsyncStorage with a clean, minimal structure:

Text
# State (one key per miniapp, includes sync metadata)
@dpage_state:{miniappId} → { state, version, lastLocalSave, lastCloudSync }

# Cache (prefixed for easy cleanup)
@dpage:projectId:m:{miniappId}:version   → version metadata
@dpage:projectId:m:{miniappId}:c:{name}  → component code

No separate index files – listing uses getAllKeys() with prefix filtering.

Cleanup APIs

TSX
// Clear state only
await PersistenceManager.clearMiniapp(miniappId);

// Clear state + cache (complete cleanup)
await PersistenceManager.clearMiniappData(miniappId);

// Clear everything
await PersistenceManager.clearAllData();

SDK exports

PropTypeDefaultDescription
initLifecycleListenerFunction-Enable auto-save on app background (call once at startup)
useNamedPersistentStateHook-useState-like hook for persistent state
MiniappStateProviderComponent-Context wrapper for miniapps
PersistenceManagerAPI-Save/restore/clear/sync APIs
CloudSyncAPI-Built-in DPage cloud sync client
clearMiniappCacheAPI-Clear cached components for a miniapp
CloudSyncCallbacks, SyncStatusTypes-Cloud sync type definitions
StateValue, StateSnapshotTypes-State type definitions

References