import { useSyncExternalStore, useEffect, useCallback, useRef } from 'react'
export type Storage = 'localStorage' | 'sessionStorage'
const subscribe = (callback: (newValue: string | null) => void) => {
const listener = (event: StorageEvent) => {
window.addEventListener('storage', listener)
window.removeEventListener('storage', listener)
const getStorageValue = ({
}: { storageKey: string; storage: Storage }) => {
return window[storage].getItem(storageKey)
const setValueToStorage = <ValueType>({
}: { storageKey: string; storage: Storage; value: ValueType }) => {
const stringyfiedValue = JSON.stringify(value)
window[storage].setItem(storageKey, stringyfiedValue)
new StorageEvent('storage', { key: storageKey, newValue: stringyfiedValue })
const removeValueFromStorage = ({
}: { storageKey: string; storage: Storage }) => {
window[storage].removeItem(storageKey)
window.dispatchEvent(new StorageEvent('storage', { key: storageKey }))
export type FunctionAsValue<ValueType> = (value: ValueType) => ValueType
const valueIsFunction = <T>(value: T | FunctionAsValue<T>): value is FunctionAsValue<T> =>
typeof value === 'function'
export function useWebStorage<StateType>({
const store = useSyncExternalStore(
() => getStorageValue({ storageKey, storage }),
() => JSON.stringify(initialState ?? null)
const storeRef = useRef(store)
const initialStateRef = useRef(initialState)
const isFirstRender = useRef(true)
getStorageValue({ storageKey, storage }) === null &&
initialStateRef.current !== undefined &&
setValueToStorage({ storageKey, storage, value: initialStateRef.current })
isFirstRender.current = false
}, [storageKey, storage])
const setItemValue = useCallback(
(value: StateType | FunctionAsValue<StateType>) => {
`Cannot set the following storage key's value to null or undefined: ${storageKey}.
If you want to remove the item from storage, use the removeItem method instead`
if (valueIsFunction(value)) {
if (storeRef.current === null && initialStateRef.current === undefined) {
`Cannot call ${value.name} as the store's value is null and no initial state was provided`
storeRef.current !== null
? value(JSON.parse(storeRef.current))
: value(initialStateRef.current as StateType)
setValueToStorage({ storageKey, storage, value: newValue })
const removeItem = useCallback(() => {
removeValueFromStorage({ storageKey, storage })
}, [storage, storageKey])
state: JSON.parse(store ?? 'null') as StateType | null,