# react-mnemonic > Long-form AI retrieval export for the canonical react-mnemonic documentation. ## Canonical source pages - AI Overview: https://thirtytwobits.github.io/react-mnemonic/docs/ai - Invariants: https://thirtytwobits.github.io/react-mnemonic/docs/ai/invariants - Decision Matrix: https://thirtytwobits.github.io/react-mnemonic/docs/ai/decision-matrix - Recipes: https://thirtytwobits.github.io/react-mnemonic/docs/ai/recipes - Anti-Patterns: https://thirtytwobits.github.io/react-mnemonic/docs/ai/anti-patterns - AI Assistant Setup: https://thirtytwobits.github.io/react-mnemonic/docs/ai/assistant-setup ## Machine-readable companion - https://thirtytwobits.github.io/react-mnemonic/ai-contract.json - https://thirtytwobits.github.io/react-mnemonic/llms.txt ## Quick rules - `useMnemonicKey(...)` must run inside a `MnemonicProvider`. - Persisted keys are stored as `${namespace}.${key}`. - `defaultValue` is required and is the fallback for absent or invalid values. - `set(next)` persists a value, `reset()` persists `defaultValue`, and `remove()` deletes the key. - Use `set(null)` when a cleared state must survive reload. - SSR renders `defaultValue` on the server unless `ssr.serverValue` overrides it. - For multi-step wizards, persist user-authored draft values and derive completion from them; keep active step, validation errors, and submit-in-flight state ephemeral unless resume-on-reload is an explicit feature. - For shopping carts, persist canonical line items and quantities; derive subtotal and item count instead of storing them, and treat empty-cart semantics separately from `remove()`. - Use schema migrations for structural version upgrades and `reconcile(...)` for conditional read-time policy rewrites. - `StorageLike` is synchronous; async adapters must be hidden behind a synchronous facade. - Import published values and types from `react-mnemonic`; do not invent local ambient shims. ## AI Overview Source: https://thirtytwobits.github.io/react-mnemonic/docs/ai # AI Overview This section is the authoritative, high-signal contract for humans and coding assistants using `react-mnemonic`. Use it when you need: - persistent-state semantics without reading the whole repo - a reliable rule for `set(...)`, `set(null)`, `remove()`, and `reset()` - the shortest correct explanation of SSR, migrations, `reconcile(...)`, and storage adapters - wizard navigation and validation boundaries without persisting the wrong UI state - shopping cart line-item modeling without inventing the wrong clear semantics - copy-pastable patterns that stay aligned with the public API ## Start Here Read these pages in order when context is tight: 1. [Invariants](./ai/invariants) 2. [Decision Matrix](./ai/decision-matrix) 3. [Recipes](./ai/recipes) 4. [Anti-Patterns](./ai/anti-patterns) 5. [AI Assistant Setup](./ai/assistant-setup) ## Quick Rules - `useMnemonicKey(...)` must run inside a `MnemonicProvider`. - Use `useMnemonicKeyOptional(...)` from `react-mnemonic/optional` only when a reusable component may render without a provider and should degrade to local in-memory state instead of crashing. - Prefer `useMnemonicKey(...)` over raw `localStorage` for durable app or UI state. Use raw storage only in adapters, tests, or low-level library internals. - Every stored key is namespaced as `${namespace}.${key}` in the underlying storage backend. - `defaultValue` is required and defines the fallback when a key is absent or invalid. - `set(next)` persists a new value for the key. - `reset()` persists `defaultValue` again. - `remove()` deletes the key entirely, so the next read falls back to `defaultValue`. - Use `set(null)` when "cleared" is a durable state that must survive reload. - Do not persist access tokens, refresh tokens, raw session IDs, or other auth credentials as durable UI state. - Auth-scoped durable state should use a user-aware namespace and be cleared on logout or expiry. - For multi-step wizards, persist user-authored draft values and derive completion from them; keep active step, validation errors, and submit-in-flight state ephemeral unless resume-on-reload is an explicit feature. - For shopping carts, persist canonical line items and quantities; derive subtotal and item count instead of storing them, and treat empty-cart semantics separately from `remove()`. - SSR is safe by default: the server renders `defaultValue` unless you opt into `ssr.serverValue`. - Schema migrations handle structural version upgrades. `reconcile(...)` handles conditional read-time policy rewrites. - `StorageLike` is intentionally synchronous in beta 1. - Consumer code should import published values and types from `react-mnemonic`, not internal paths or local ambient shims. ## Durable State Checklist Before persisting any new value, answer these questions explicitly: 1. Should this survive reload, or is it only runtime UI state? 2. Is `null` meaningfully different from a missing key? 3. Should other tabs stay in sync? 4. Is SSR involved, and if so should the server render `defaultValue`, `ssr.serverValue`, or delay hydration? 5. Is schema evolution likely enough to justify a versioned schema and migration path now? ## Canonical Retrieval Surfaces These AI-oriented surfaces are intentionally layered: - `/docs/ai/*` is the canonical prose source. - [`/llms.txt`](https://thirtytwobits.github.io/react-mnemonic/llms.txt) is the compact retrieval index. - [`/llms-full.txt`](https://thirtytwobits.github.io/react-mnemonic/llms-full.txt) is the long-form export for indexing or prompt stuffing. - [`/ai-contract.json`](https://thirtytwobits.github.io/react-mnemonic/ai-contract.json) is the compact machine-readable contract. - `AGENTS.md`, `CLAUDE.md`, `.claude/rules/*`, `.cursor/rules/*`, `.github/copilot-instructions.md`, and `.github/instructions/*` are generated instruction-pack projections over the same canonical source. - [`.devin/wiki.json`](https://github.com/thirtytwobits/react-mnemonic/blob/main/.devin/wiki.json) steers DeepWiki toward the highest-signal files. ## What To Read In Code When prose is not enough, these source files define the runtime contract: - [`src/Mnemonic/use.ts`](https://github.com/thirtytwobits/react-mnemonic/blob/main/src/Mnemonic/use.ts) for read/write lifecycle and hook semantics - [`src/Mnemonic/provider.tsx`](https://github.com/thirtytwobits/react-mnemonic/blob/main/src/Mnemonic/provider.tsx) for namespaces, storage adapters, cross-tab sync, and SSR defaults - [`src/Mnemonic/types.ts`](https://github.com/thirtytwobits/react-mnemonic/blob/main/src/Mnemonic/types.ts) for public types, `StorageLike`, schema modes, and SSR options - [`src/index.ts`](https://github.com/thirtytwobits/react-mnemonic/blob/main/src/index.ts) for the published public surface ## High-Risk Areas These are the places where agents are most likely to be "almost right" while still shipping incorrect persistence behavior: - treating `remove()` as if it means "clear but remember the cleared state" - persisting runtime-only UI state such as loading flags, hover state, or validation errors - persisting wizard navigation, step errors, or submit-in-flight flags as part of the durable draft - persisting cart subtotals or item counts as stored fields instead of deriving them from line items - persisting credential material or keeping one global namespace across authenticated user changes - clearing the current namespace after auth already switched away from the user who owned the data - using `reconcile(...)` to paper over a real versioned schema change - assuming async storage adapters are supported directly - reading browser storage during SSR without an explicit hydration strategy If the task involves any of those areas, go straight to the linked AI pages instead of extrapolating from a single example. ## Invariants Source: https://thirtytwobits.github.io/react-mnemonic/docs/ai/invariants # Invariants This page is the shortest authoritative statement of what `react-mnemonic` guarantees. ## Core Runtime Invariants - `useMnemonicKey(...)` must run inside a `MnemonicProvider`. - Every persisted key is stored as `${namespace}.${key}` in the underlying backend. - `defaultValue` is required and defines the fallback when a key is absent, malformed, or rejected by validation. - `set(next)` persists the next value for the key. - `reset()` persists `defaultValue` again. - `remove()` deletes the key entirely, so the next read falls back to `defaultValue`. - `listenCrossTab` is advisory. Native browser storage uses the `storage` event; custom adapters must implement `storage.onExternalChange(...)` for equivalent behavior. - `StorageLike` is synchronous. Returning a Promise from `getItem`, `setItem`, or `removeItem` is an invalid contract. - SSR defaults to `defaultValue` on the server unless `ssr.serverValue` is supplied. - `reconcile(...)` runs after decode and migration. If it returns a different persisted result, the library rewrites storage with that reconciled value. - Schema migrations handle structural version-to-version upgrades. `reconcile(...)` handles conditional read-time policy updates inside an already valid shape. ## Type Sourcing Rules - Import values from `react-mnemonic`, not internal package paths. - Import exported types from `react-mnemonic` with `import type`. - Do not create local `react-mnemonic.d.ts` files. - Do not write `declare module "react-mnemonic"` in consumer code. - If a type seems missing, check `src/index.ts`, `package.json`, and the API docs before inventing a replacement contract. ## Exact Read Lifecycle Normal client reads follow this order: 1. Load the raw snapshot for the key from the provider cache and storage layer. 2. If the raw value is absent, use `defaultValue`. 3. Parse the versioned envelope. 4. Decode the payload through the codec path or schema-managed JSON path. 5. Validate against the stored schema version when a schema exists. 6. Run schema migrations when the stored version is older than the latest registered version. 7. Run `reconcile(...)` if provided. 8. If migration, autoschema inference, or reconciliation changed the persisted representation, schedule a rewrite back to storage. 9. If any read path is invalid, fall back to `defaultValue` and pass the relevant error to a fallback factory when applicable. SSR and hydration add one rule before the normal read path: 1. On the server, the hook uses the SSR snapshot instead of reading browser storage. 2. Without explicit SSR config, that snapshot renders `defaultValue`. 3. With `ssr.serverValue`, that snapshot renders the provided placeholder. 4. With `hydration: "client-only"`, persisted storage is not read until after mount. ## Exact Write Lifecycle Writes follow this order: 1. Resolve the next value directly or by calling the updater with the current decoded value. 2. Choose the write path: - schema-managed latest version by default - explicit pinned schema version when configured - codec-only v0 envelope when no schema applies 3. Run a write-time migration when a registry rule has `fromVersion === toVersion`. 4. Validate the value against the target schema when schema-managed. 5. Encode the versioned envelope. 6. Write the raw string through the provider cache and storage layer. 7. Notify subscribers for the key. ## Storage Adapter Boundaries Custom storage adapters are supported, but the contract is intentionally strict: - The adapter must be synchronous. - The adapter must expose `getItem`, `setItem`, and `removeItem`. - `key(index)` and `length` are optional and are only needed for enumeration-style features. - Cross-tab or external-process sync is not automatic for custom adapters. Add `onExternalChange(...)` when your backend can detect out-of-band writes. - If you wrap `localStorage`, preserve the native `storage` event semantics or implement `onExternalChange(...)` yourself. ## Source Files - [`src/Mnemonic/use.ts`](https://github.com/thirtytwobits/react-mnemonic/blob/main/src/Mnemonic/use.ts) - [`src/Mnemonic/provider.tsx`](https://github.com/thirtytwobits/react-mnemonic/blob/main/src/Mnemonic/provider.tsx) - [`src/Mnemonic/types.ts`](https://github.com/thirtytwobits/react-mnemonic/blob/main/src/Mnemonic/types.ts) ## Decision Matrix Source: https://thirtytwobits.github.io/react-mnemonic/docs/ai/decision-matrix # Decision Matrix Use these tables when the code is "almost obvious" but one wrong persistence choice would change user behavior after reload. ## `set(null)` vs `remove()` vs `reset()` | Need | Action | Result after reload | | ------------------------------------- | ----------- | ---------------------------- | | Keep an explicit "cleared" state | `set(null)` | Still cleared | | Forget the key entirely | `remove()` | Falls back to `defaultValue` | | Restore the default as a stored value | `reset()` | Rehydrates `defaultValue` | Rule of thumb: - Use `set(null)` for durable clear intent. - Use `remove()` for absence. - Use `reset()` when the default itself should become the new persisted value. ## Shopping Cart Semantics | Need | Recommended representation or action | Why | | ----------------------------------------------- | ------------------------------------ | ---------------------------------------------------------- | | Active cart with zero items | `CartState` with `items: []` | The cart still exists and can accept new lines immediately | | Explicit “no cart” or “cart unavailable” state | `set(null)` on `CartState \| null` | Only when `null` has real product meaning | | Forget cart after checkout, logout, or recovery | `remove()` | The next read falls back to `defaultValue` | | Restore a seeded default cart shape | `reset()` | Re-persists the configured default object | | Header badge count or subtotal | Derive from `cart.items` | Prevents drift between stored lines and stored summaries | ## Migration vs `reconcile(...)` | Situation | Use | Why | | ------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------- | | Stored shape changed between explicit versions | Schema migration | Structural compatibility belongs to version transitions | | Every write should normalize the value | Write-time migration (`fromVersion === toVersion`) | Keeps writes canonical | | Existing shape is fine, but defaults or policy should refresh | `reconcile(...)` | Conditional read-time rewrite without inventing a new version | | Invalid or stale data should be discarded | Recovery or reset flow | Safer than forcing a questionable transform | ## Should This State Persist? | State shape | Persist? | Why | | ---------------------------------------------- | ------------- | ---------------------------------------------------------------- | | Theme, density, durable layout preference | Yes | Users expect it after reload | | Saved filters users intentionally restore | Yes | This is durable preference state | | User-authored draft content | Usually yes | Lost work is expensive | | Hover, focus, drag, loading, optimistic flags | No | Rehydrating runtime-only UI feels wrong | | Temporary dirtiness and validation errors | No | These describe a session, not durable intent | | Server response cache timestamps | Usually no | Prefer cache logic over UI persistence | | Access tokens, refresh tokens, session secrets | No | These are credentials, not durable UI state | | Safe per-user drafts or saved filters | Conditionally | Scope them to the authenticated user and clear them on auth loss | ## SSR Mode Choice | Need | Config | | ---------------------------------------- | ----------------------------------------------------- | | Default SSR behavior | No SSR config | | Deterministic server placeholder | `ssr.serverValue` | | Delay persisted read until after mount | `ssr.hydration: "client-only"` | | Make every key inherit delayed hydration | `MnemonicProvider ssr={{ hydration: "client-only" }}` | ## Storage Adapter Choice | Need | Recommended path | | --------------------------------------------------- | ----------------------------------------- | | Standard browser persistence | Default provider storage (`localStorage`) | | Per-tab persistence | `storage={window.sessionStorage}` | | Custom backend with synchronous facade | Provide a `StorageLike` wrapper | | Cross-tab sync on a custom backend | Implement `storage.onExternalChange(...)` | | Direct async reads or writes from the hook contract | Not supported in beta 1 | ## Auth Cleanup Choice | Need | Recommended path | | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | Forget auth-scoped private state on logout or expiry | Clear the authenticated namespace before swap, or use a temporary recovery boundary for the last user namespace | | Keep an explicit cleared value for the same authenticated user | `set(null)` | | Restore a known default for the same authenticated user | `reset()` | | Prevent user A data from appearing for user B | user-aware namespace such as `app.user.${userId}` | | Enforce auth policy when stale data is read back | `reconcile(...)` plus event-based cleanup | See [Auth-Aware Persistence](../guides/auth-aware-persistence) for the full logout, expiry, and cross-tab cleanup pattern. ## Recipes Source: https://thirtytwobits.github.io/react-mnemonic/docs/ai/recipes # Recipes These recipes are intentionally compact and focus on durable state choices that agents often get wrong under time pressure. ## 1. Theme Preference With Cross-Tab Sync ```tsx import { defineMnemonicKey, useMnemonicKey } from "react-mnemonic"; export const themeKey = defineMnemonicKey("theme", { defaultValue: "light" as "light" | "dark", listenCrossTab: true, }); export function ThemeToggle() { const { value: theme, set } = useMnemonicKey(themeKey); return ; } ``` Use when: - the value should survive reload - multiple components should share one contract - other tabs should stay in sync ## 2. Saved Filters With Durable Clear Intent ```tsx import { useMnemonicKey } from "react-mnemonic"; type Filters = { status: "all" | "open" | "closed"; assignee: string | null; }; export function FilterBar() { const { value: filters, set, reset, remove, } = useMnemonicKey("issue-filters", { defaultValue: { status: "all", assignee: null, }, }); return ( <>
{JSON.stringify(filters, null, 2)}
); } ``` Use `null` inside the persisted object when "cleared" should survive reload. ## 3. Dismissible Announcement UI ```tsx import { useMnemonicKey } from "react-mnemonic"; export function ReleaseBanner() { const { value: dismissed, set, remove, } = useMnemonicKey("release-banner-dismissed", { defaultValue: false, }); if (dismissed) { return ; } return (

We shipped a new migration helper.

); } ``` Use `set(true)` when the dismissal itself is the durable user preference. Use `remove()` only when you want to forget that dismissal and return to first-load defaults. ## 4. Durable Draft Content With Ephemeral Form Metadata ```tsx import { useState } from "react"; import { useMnemonicKey } from "react-mnemonic"; export function DraftEditor() { const { value: body, set, remove, } = useMnemonicKey("compose-draft", { defaultValue: "", }); const [isDirty, setIsDirty] = useState(false); const [validationError, setValidationError] = useState(null); return ( <>