Hooks

useParameterPersistence

Persist a serializable view-parameter snapshot (search, filters, sort, column layout, …) per saved view and restore it on the next mount, backed by a synchronous native key-value store.

iOSAndroidExpo Go

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-parameter-persistence

The module exports the useParameterPersistence hook and the helpers the Docyrus listing hooks share: resolvePersistConfig, buildPersistKey, getPersistStore, plus the PersistStateProp types. It is the engine behind the persistState option of the Docyrus listing hooks.

Overview

The hook takes a snapshot object, restores a stored copy once per storage key, and writes changes back after a debounce.

  • Restore once per key. When storageKey changes (for example the user switches to another saved view), the stored overlay for the new key is restored.
  • No write before restore. The first snapshot for a key never overwrites stored state before that key has been restored.
  • Ordering. Declare the hook after any effect that applies a saved view, so the restored overlay lands on top of the view preset in the same commit.
  • Failure tolerant. Corrupt JSON, a failing store or a serialization error is ignored.

Storage on React Native

React Native has no localStorage / sessionStorage, so storage resolves through the package's lib/storage module:

ModeBackend
'session' (default)In-memory Map that lives as long as the JS runtime (until the app is killed or reloaded).
'local'The store you register with <DocyStorageProvider> or setDocyLocalStore(). Without one: globalThis.localStorage if present (after import 'expo-sqlite/localStorage/install'), otherwise a separate in-memory Map.

The contract is synchronous (getItem / setItem / removeItem), because the listing hooks read their state during mount. Recommended backends are expo-sqlite/kv-store (works in Expo Go) and react-native-mmkv (dev build). AsyncStorage is asynchronous and does not fit.

Mount <DocyStorageProvider> above every Docyrus listing hook. It registers the store synchronously during render, because child mount effects run before parent effects.

import Storage from 'expo-sqlite/kv-store';

import { DocyStorageProvider, type DocyKeyValueStore } from '@/lib/docyrus/storage';

const kvStore: DocyKeyValueStore = {
  getItem: key => Storage.getItemSync(key),
  setItem: (key, value) => Storage.setItemSync(key, value),
  removeItem: (key) => {
    Storage.removeItemSync(key);
  }
};

export function AppProviders({ children }: { children: React.ReactNode }) {
  return <DocyStorageProvider store={kvStore}>{children}</DocyStorageProvider>;
}

lib/storage also exports setDocyLocalStore(store | null) (register outside React), getDocyStore(kind), getInjectedDocyLocalStore() and createMemoryStore().

Usage

import { useCallback, useMemo, useState } from 'react';

import {
  buildPersistKey,
  resolvePersistConfig,
  useParameterPersistence,
  type PersistStateProp
} from '@/hooks/docyrus-native/use-parameter-persistence';

type ListParams = { search: string; sort: string };

export function useContactListParams(viewId: string | null, persistState?: PersistStateProp) {
  const [search, setSearch] = useState('');
  const [sort, setSort] = useState('name');

  const config = resolvePersistConfig(persistState);
  const storageKey = config && viewId
    ? buildPersistKey(['crm', config.key || 'contacts', viewId])
    : null;

  const snapshot = useMemo<ListParams>(() => ({ search, sort }), [search, sort]);

  const onRestore = useCallback((restored: Partial<ListParams>) => {
    if (restored.search !== undefined) setSearch(restored.search);
    if (restored.sort !== undefined) setSort(restored.sort);
  }, []);

  useParameterPersistence<ListParams>({
    enabled: Boolean(config),
    storage: config?.storage ?? 'session',
    storageKey,
    ready: viewId !== null,
    snapshot,
    onRestore
  });

  return { search, setSearch, sort, setSort };
}

API Reference

useParameterPersistence(args)

Returns void.

ArgumentTypeDefaultDescription
enabledboolean—Whether persistence is active at all.
storageParameterPersistenceStorage—'session' or 'local'.
storageKeystring | null—Full storage key including the active view id. null suspends restore and write until it resolves.
readyboolean—Defers restore and write until the surrounding view layer has settled (fields loaded, saved view applied).
snapshotT extends object—Current serializable parameter snapshot. Memoize it; it is serialized with JSON.stringify on change.
onRestore(restored: Partial<T>) => void—Applies a stored snapshot back into state. Read through a ref, so an inline function is fine.
debounceMsnumber300Debounce for writes.

resolvePersistConfig(prop)

ParameterTypeDescription
propPersistStateProp | undefinedThe public persistState option.

Returns: { storage: ParameterPersistenceStorage; key: string } | null. true becomes { storage: 'session', key: '' }; an object fills storage (default 'session') and key (default ''); falsy returns null.

buildPersistKey(parts)

ParameterTypeDescription
partsReadonlyArray<string | undefined | null>Key segments. Falsy parts are dropped.

Returns: string, e.g. docyrus:view-params:<appSlug>:<dataSourceSlug>:<viewId>.

getPersistStore(storage)

ParameterTypeDescription
storageParameterPersistenceStorage'session' or 'local'.

Returns: DocyKeyValueStore | null. On native a store is always resolved (see the storage table); null only if resolving throws. Shared with the saved-view hooks so every layer resolves storage the same way.

Type Exports

TypeDescription
ParameterPersistenceStorage'session' | 'local'.
ParameterPersistenceConfig{ storage?: ParameterPersistenceStorage; key?: string }. key overrides the data-source segment so two listings of the same data source don't share state.
PersistStatePropboolean | ParameterPersistenceConfig. true is shorthand for { storage: 'session' }.

On this page