Does zustand's persist middleware lose Date, Map, and Set types across a JSON round-trip?
0 reputation · 14 Dec 2025, 18:30 UTC
0 reputation · 14 Dec 2025, 18:30 UTC
A client-side draft-autosave store built with zustand's persist middleware (assuming the current 5.x line, with 4.x behaving the same) has to survive a page reload. Part of its state is not JSON-native: a last-edited Date, a Set of selected ids, and a Map used as a keyed cache.
The middleware's default storage path serializes through JSON.stringify and parses back with JSON.parse, and JSON has no representation for those types. The documented escape hatches — partialize to narrow what is stored, a custom storage adapter via createJSONStorage, and the rehydration hooks — leave open which layer should own type revival, and whether that choice changes when the storage engine is asynchronous and rehydration finishes after the first render.
Before settling on a design, three points need pinning down:
hasHydrated, onFinishHydration) dependable for gating the first render?35275 reputation · 14 Dec 2025, 19:14 UTC
Yes. With the default persist middleware the state is passed through JSON.stringify and JSON.parse. JSON has no native representation for Date, Set or Map, so after rehydration you get:
Date → an ISO‑8601 string (e.g. "2026-10-12T06:13:45.920Z")Set → a plain object {} (its entries are lost)Map → a plain object {} (its entries are lost)The middleware’s default storage (via createJSONStorage) does not perform any type revival; it only stores and retrieves the raw JSON. No built‑in hook reconstructs the original types.
serialize/deserialize functions supplied to createJSONStorage – they run synchronously for storages like localStorage.onFinishHydration (or a custom merge) that walks the rehydrated state and converts the plain values back.If revival is performed synchronously in the storage adapter’s deserialize, the store’s hasHydrated flag is set before the first render, so you can gate UI on it safely. If you rely on an asynchronous storage (e.g., IndexedDB via a custom async adapter) or place revival in onFinishHydration, you must wait for that hook to resolve before accessing the revived values; otherwise you will see the interim string/object placeholders.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.