Persistent Data Reference
Use usePersistentItem as the app's database hook. It is the persistence layer for user data that should survive reloads, sync across devices, and remain private to each user. Browser storage APIs such as localStorage, cookies, and IndexedDB are blocked in the sandbox.
Each app stores data in a user-private key-value JSON document. usePersistentItem reads and writes one key in that document.
usePersistentItem
Import the hook from the persistent item module:
import { usePersistentItem } from "@/hooks/use-persistent-item";
function Controls() {
const [, setCount] = usePersistentItem<number>("count", 0);
return (
<div className="flex gap-2">
<button onClick={() => setCount((current) => current - 1)}>-</button>
<button onClick={() => setCount((current) => current + 1)}>+</button>
</div>
);
}
export default function App() {
const [count] = usePersistentItem<number>("count", 0);
return (
<div className="space-y-4">
<h2 className="text-2xl font-semibold">{count}</h2>
<Controls />
</div>
);
}
Save Status and Errors
setValue updates local UI immediately. Persistence is queued and saved in the background. Use the metadata object when the app needs to show save state or recover from a failed save.
const [notes, setNotes, { status, error, retry, clearError }] =
usePersistentItem<Note[]>("notes", []);
const addNote = (text: string) => {
setNotes((current) => [
...current,
{ id: crypto.randomUUID(), text, createdAt: new Date().toISOString() },
]);
};
return (
<div>
{status === "saving" && <p className="text-muted-foreground">Saving...</p>}
{error && (
<div className="text-destructive">
<p>{error.message}</p>
<button onClick={retry}>Retry</button>
<button onClick={clearError}>Dismiss</button>
</div>
)}
</div>
);
Best Practices
- Choose stable, descriptive keys such as
tasks,settings, orgenerationHistory. - Use updater functions when the next value depends on the previous value.
- Store dates as UTC ISO strings and parse them when displaying.
- Persist small JSON state. For files, media, and large generated assets, store uploaded or generated URLs instead of raw binary or base64 data.
- Keep temporary form state in
React.useState, then write tousePersistentItemon submit. - Update persistent data from event handlers, AI hook callbacks, or completed workflows instead of mirroring every render through
useEffect. - Share keys across components intentionally. Accidental key reuse can make unrelated components overwrite each other.
- Use
meta.statusandmeta.errorfor important save flows, but rely on automatic background persistence for ordinary interactions.