# 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 ## Quick Start The minimum correct shape is: mount a `MnemonicProvider` above every component that calls `useMnemonicKey(...)`. ```tsx // main.tsx import React from "react"; import ReactDOM from "react-dom/client"; import { MnemonicProvider } from "react-mnemonic"; import { App } from "./App"; ReactDOM.createRoot(document.getElementById("root")!).render( , ); ``` Any descendant of `App` can now call `useMnemonicKey(...)`. Without the provider, the required-provider entrypoints throw. ## 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`. - `set(...)` never throws when storage rejects the write. Use `useMnemonicRecovery().unpersistedKeys()` to detect dropped writes and `flush()` to retry them once space frees up. - Pass `onStorageError` to `MnemonicProvider` to be told the moment a write is dropped, with a `reason` of `"quota"`, `"access"`, `"schema"`, `"codec"`, `"contract"`, or `"unknown"`. Without it the only signal is a `console.error` no user will see. - 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. If the storage backend rejected the write, queue the key as unpersisted. Otherwise clear any entry queued for it. 8. Notify subscribers for the key. 9. If the write was dropped, call `onStorageError` with the classified reason. The cache is updated whether or not step 6 reached storage, so a rejected write is still what components see. Rejection is reported through `useMnemonicRecovery().unpersistedKeys()` and retried through `flush(...)`. A queued entry clears when the key is written successfully, when a flush persists it, or when an external change reloads that key from storage. Step 9 runs last so the handler observes a fully applied mutation. For failures at step 6 it fires exactly when step 7 queued the key, so the two stay in step: `onStorageError` says a write was dropped, `unpersistedKeys()` says it is still outstanding, and `flush(...)` retries it. `onStorageError` is strictly wider than `unpersistedKeys()`. Failures that never reach step 6 — schema rejection at step 4, encode failure at step 5 — are reported through the same callback with reason `"schema"`, `"codec"`, or `"unknown"`, but the write never happened: the cache still holds the previous value, no key is queued, and `flush(...)` has nothing to retry. Treat the storage-layer reasons (`"quota"`, `"access"`, `"contract"`) as recoverable and the pre-storage reasons as application bugs. ## 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 // theme-key.ts import { defineMnemonicKey } from "react-mnemonic"; export const themeKey = defineMnemonicKey("theme", { defaultValue: "light" as "light" | "dark", listenCrossTab: true, }); ``` ```tsx // theme-bootstrap.ts import { applyMnemonicBootstrap, recallMnemonic } from "react-mnemonic/bootstrap"; import { themeKey } from "./theme-key"; export const themeBootstrap = recallMnemonic({ namespace: "app", keys: [themeKey] as const, }); applyMnemonicBootstrap({ snapshot: themeBootstrap, apply: ({ theme }) => { document.documentElement.dataset.theme = theme; }, }); ``` ```tsx // app.tsx import { MnemonicProvider, useMnemonicKey } from "react-mnemonic"; import { themeBootstrap } from "./theme-bootstrap"; import { themeKey } from "./theme-key"; export function ThemeToggle() { const { value: theme, set } = useMnemonicKey(themeKey); return ; } export function App() { return ( ); } ``` Use when: - the value should survive reload - multiple components should share one contract - other tabs should stay in sync - the page should avoid a light/dark flash on first paint Run the bootstrap module before React first renders, such as in a small entry module loaded before `createRoot(...)` or `hydrateRoot(...)`. ## 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 ( <>