diff --git a/docs/utilities/storage.mdx b/docs/utilities/storage.mdx
index b0c9bdb1a6..56e758b85e 100644
--- a/docs/utilities/storage.mdx
+++ b/docs/utilities/storage.mdx
@@ -49,6 +49,26 @@ If not specified, the default storage implementation uses `localStorage` for sto
+### `createJSONStorage` util
+
+To create a custom storage implementation with `JSON.stringify()`/`JSON.parse()` for the `storage` option, `createJSONStorage` util is provided.
+
+Usage:
+
+```js
+const storage = createJSONStorage(
+ // getStringStorage
+ () => localStorage, // or sesseionStorage, asyncStorage or alike
+ // options (optional)
+ {
+ reviver, // optional reviver option for JSON.parse
+ replacer, // optional replacer option for JSON.stringify
+ },
+)
+```
+
+Note: `JSON.parse` is not type safe. If it can't accept any types, some kind of validation would be necessary for production apps.
+
### Server-side rendering
Any JSX markup that depends on the value of a stored atom (e.g., a `className` or `style` prop) will use the `initialValue` when rendered on the server (since `localStorage` and `sessionStorage` are not available on the server).
diff --git a/src/vanilla/utils/atomWithStorage.ts b/src/vanilla/utils/atomWithStorage.ts
index b07ff454b7..147eeab606 100644
--- a/src/vanilla/utils/atomWithStorage.ts
+++ b/src/vanilla/utils/atomWithStorage.ts
@@ -46,16 +46,23 @@ export interface SyncStringStorage {
removeItem: (key: string) => void
}
+type JsonStorageOptions = {
+ reviver?: (key: string, value: unknown) => unknown
+ replacer?: (key: string, value: unknown) => unknown
+}
export function createJSONStorage(
getStringStorage: () => AsyncStringStorage,
+ options?: JsonStorageOptions,
): AsyncStorage
export function createJSONStorage(
getStringStorage: () => SyncStringStorage,
+ options?: JsonStorageOptions,
): SyncStorage
export function createJSONStorage(
getStringStorage: () => AsyncStringStorage | SyncStringStorage | undefined,
+ options?: JsonStorageOptions,
): AsyncStorage | SyncStorage {
let lastStr: string | undefined
let lastValue: any
@@ -65,7 +72,7 @@ export function createJSONStorage(
str = str || ''
if (lastStr !== str) {
try {
- lastValue = JSON.parse(str)
+ lastValue = JSON.parse(str, options?.reviver)
} catch {
return initialValue
}
@@ -80,7 +87,10 @@ export function createJSONStorage(
return parse(str)
},
setItem: (key, newValue) =>
- getStringStorage()?.setItem(key, JSON.stringify(newValue)),
+ getStringStorage()?.setItem(
+ key,
+ JSON.stringify(newValue, options?.replacer),
+ ),
removeItem: (key) => getStringStorage()?.removeItem(key),
}
if (