import { useMemo, useRef, useSyncExternalStore } from "react"; type HookStorageItem = { get: () => T; set: StorageSetter; subscribe: (callback: () => void) => () => void; merge?: (partial: Partial) => void; reset?: () => void; delete?: () => void; setOrDelete?: (value: T | null | undefined) => void; }; type ReadableStorageItem = { get: () => T; subscribe: (callback: () => void) => () => void; }; export type StorageSetter = (value: T | ((prev: T) => T)) => void; export type StorageActions = { set: StorageSetter; merge: (partial: Partial) => void; reset: () => void; remove: () => void; setOrDelete: (value: T | null | undefined) => void; }; export function useStorageActions( item: HookStorageItem, ): StorageActions { return useMemo>( () => ({ set: item.set, merge: (partial) => item.merge?.(partial), reset: () => item.reset?.(), remove: () => item.delete?.(), setOrDelete: (value) => item.setOrDelete?.(value), }), [item], ); } export function useStorage( item: HookStorageItem, ): [T, StorageSetter, StorageActions] { const value = useSyncExternalStore(item.subscribe, item.get, item.get); const actions = useStorageActions(item); return [value, item.set, actions]; } export function useStorageValue(item: ReadableStorageItem): T { return useSyncExternalStore(item.subscribe, item.get, item.get); } export function useStorageSelector( item: HookStorageItem, selector: (value: T) => TSelected, isEqual: (prev: TSelected, next: TSelected) => boolean = Object.is, ): [TSelected, StorageSetter] { const selectedRef = useRef< { hasValue: false } | { hasValue: true; value: TSelected } >({ hasValue: false, }); const getSelectedSnapshot = () => { const nextSelected = selector(item.get()); const current = selectedRef.current; if (current.hasValue && isEqual(current.value, nextSelected)) { return current.value; } selectedRef.current = { hasValue: true, value: nextSelected }; return nextSelected; }; const selectedValue = useSyncExternalStore( item.subscribe, getSelectedSnapshot, getSelectedSnapshot, ); return [selectedValue, item.set]; } export function useSetStorage(item: HookStorageItem): StorageSetter { return item.set; }