{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "createUseStorageState", "title": "createUseStorageState", "description": "createUseStorageState hook", "registryDependencies": [ "https://shadcn-ahooks.vercel.app/r/utils.json", "https://shadcn-ahooks.vercel.app/r/useEventListener.json", "https://shadcn-ahooks.vercel.app/r/useMemoizedFn.json", "https://shadcn-ahooks.vercel.app/r/useUpdateEffect.json" ], "files": [ { "path": "packages/hooks/src/createUseStorageState/index.ts", "content": "import { useState } from 'react';\nimport useEventListener from '../useEventListener';\nimport useMemoizedFn from '../useMemoizedFn';\nimport useUpdateEffect from '../useUpdateEffect';\nimport { isFunction, isUndef } from '../utils';\n\nexport const SYNC_STORAGE_EVENT_NAME = 'AHOOKS_SYNC_STORAGE_EVENT_NAME';\n\nexport type SetState = S | ((prevState?: S) => S);\n\nexport interface Options {\n defaultValue?: T | (() => T);\n listenStorageChange?: boolean;\n serializer?: (value: T) => string;\n deserializer?: (value: string) => T;\n onError?: (error: unknown) => void;\n}\n\nexport function createUseStorageState(getStorage: () => Storage | undefined) {\n function useStorageState(key: string, options: Options = {}) {\n let storage: Storage | undefined;\n const {\n listenStorageChange = false,\n onError = (e) => {\n console.error(e);\n },\n } = options;\n\n // https://github.com/alibaba/hooks/issues/800\n try {\n storage = getStorage();\n } catch (err) {\n onError(err);\n }\n\n const serializer = (value: T) => {\n if (options.serializer) {\n return options.serializer(value);\n }\n return JSON.stringify(value);\n };\n\n const deserializer = (value: string) => {\n if (options.deserializer) {\n return options.deserializer(value);\n }\n return JSON.parse(value);\n };\n\n function getStoredValue() {\n try {\n const raw = storage?.getItem(key);\n if (raw) {\n return deserializer(raw);\n }\n } catch (e) {\n onError(e);\n }\n if (isFunction(options.defaultValue)) {\n return options.defaultValue();\n }\n return options.defaultValue;\n }\n\n const [state, setState] = useState(getStoredValue);\n\n useUpdateEffect(() => {\n setState(getStoredValue());\n }, [key]);\n\n const updateState = (value: SetState) => {\n const currentState = isFunction(value) ? value(state) : value;\n\n if (!listenStorageChange) {\n setState(currentState);\n }\n\n try {\n let newValue: string | null;\n const oldValue = storage?.getItem(key);\n\n if (isUndef(currentState)) {\n newValue = null;\n storage?.removeItem(key);\n } else {\n newValue = serializer(currentState);\n storage?.setItem(key, newValue);\n }\n\n dispatchEvent(\n // send custom event to communicate within same page\n // importantly this should not be a StorageEvent since those cannot\n // be constructed with a non-built-in storage area\n new CustomEvent(SYNC_STORAGE_EVENT_NAME, {\n detail: {\n key,\n newValue,\n oldValue,\n storageArea: storage,\n },\n }),\n );\n } catch (e) {\n onError(e);\n }\n };\n\n const syncState = (event: StorageEvent) => {\n if (event.key !== key || event.storageArea !== storage) {\n return;\n }\n\n setState(getStoredValue());\n };\n\n const syncStateFromCustomEvent = (event: CustomEvent) => {\n syncState(event.detail);\n };\n\n // from another document\n useEventListener('storage', syncState, {\n enable: listenStorageChange,\n });\n\n // from the same document but different hooks\n useEventListener(SYNC_STORAGE_EVENT_NAME, syncStateFromCustomEvent, {\n enable: listenStorageChange,\n });\n\n return [state, useMemoizedFn(updateState) as (value: SetState) => void] as const;\n }\n\n return useStorageState;\n}\n", "type": "registry:file", "target": "src/hooks/ahooks/createUseStorageState/index.ts" } ], "type": "registry:hook" }