Skip to main content

State and persistence

Import store factories from @omnitron-dev/prism/state. They wrap Zustand with Immer updates, optional persistence and DevTools integration. Use these stores for local interface state. Keep remote query results in Netron's query cache, which owns their refresh and invalidation rules.

Choose a store​

FactoryUse it forPersistence
createUIStore(initializer, name)Panels, selection and transient controlsDisabled
createPrismStore(initializer, options)A store with explicit middleware choicesOpt-in through options.persist
createPersistedStore(initializer, name, persistOptions?)Preferences that survive reloadsEnabled; exposes the persist API
import { createUIStore } from '@omnitron-dev/prism/state';

interface Selection {
selectedId: string | null;
select: (id: string | null) => void;
}

export const useSelection = createUIStore<Selection>((set) => ({
selectedId: null,
select: (id) => set((state) => { state.selectedId = id; }),
}), 'selection');

Read the smallest slice in a component:

function SelectionLabel() {
const selectedId = useSelection((state) => state.selectedId);
return <span>{selectedId ?? 'Nothing selected'}</span>;
}

Outside React, use the returned store's getState() and subscribe(). Call the unsubscribe function when the consumer is disposed.

Built-in stores​

HookResponsibility
useSettingsStoreTheme preferences, density, navigation and application settings
useThemeStoreThe standalone theme store; do not create a second source of truth for a PrismProvider tree
useUIStoreShared interface state
useAuthStoreLocal authentication presentation state

Each also exports a non-hook alias: settingsStore, themeStore, uiStore and authStore. For a provider-backed application, follow the theme guide and update useSettingsStore. Authentication state in a browser store does not enforce server access; use the Netron auth provider for RPC sessions.

Persist preferences deliberately​

import { createPersistedStore } from '@omnitron-dev/prism/state';

interface Preferences {
compact: boolean;
setCompact: (compact: boolean) => void;
}

export const usePreferences = createPersistedStore<Preferences>(
(set) => ({
compact: false,
setCompact: (compact) => set((state) => { state.compact = compact; }),
}),
'preferences',
{ version: 1 },
);

The persisted factory catches unavailable-storage errors. Its default migration retains fields whose names and basic shapes still match the current defaults. A changed domain model needs an explicit migration: matching primitive types does not establish semantic compatibility.

The persist surface includes hasHydrated, rehydrate, clearStorage, onHydrate, onFinishHydration, getOptions and setOptions. Check hydration before rendering a preference-dependent screen; dispose the hydration listeners on unmount.

For structured migrations, the same subpath exports runMigrations, createVersionedStorage, createVersionedPersistOptions, createMigration, createRenameMigration, createAddFieldMigration, createRemoveFieldMigration and createTransformMigration. validateMigrations and getMigrationInfo help inspect a migration chain. exportSettings, importSettings, checkSettingsVersion, clearSettings and createSettingsValidator support preference import and recovery. Consult the API index for each current signature.

Server rendering and tests​

Create request-specific stores on the server; a module-level mutable store can otherwise share user state between requests. Browser persistence is available only after hydration. Render a stable initial state on both sides, then apply persisted preferences.

Test initial state, actions, reload, a version upgrade and unavailable storage. Use isolated storage keys and reset stores between tests. Never put passwords, private keys or an authoritative permission decision in a persisted UI store.