useWebStorage
The useWebStorage hook provides a way to synchronize state with the browser’s localStorage or sessionStorage. It allows you to easily get, set, and remove items from storage while keeping the state in sync across different tabs or windows.
Add the utility
npx @ivnatsr/ezreact add use-web-storageimport { useSyncExternalStore, useEffect, useCallback, useRef } from 'react'
const subscribe = (callback) => {  const listener = (event) => {    callback(event.newValue)  }  window.addEventListener('storage', listener)
  return () => {    window.removeEventListener('storage', listener)  }}
const getStorageValue = ({ storageKey, storage }) => {  return window[storage].getItem(storageKey)}
const setValueToStorage = ({ storageKey, storage, value }) => {  const stringyfiedValue = JSON.stringify(value)  window[storage].setItem(storageKey, stringyfiedValue)
  window.dispatchEvent(    new StorageEvent('storage', { key: storageKey, newValue: stringyfiedValue })  )}
const removeValueFromStorage = ({ storageKey, storage }) => {  window[storage].removeItem(storageKey)  window.dispatchEvent(new StorageEvent('storage', { key: storageKey }))}
const valueIsFunction = (value) => typeof value === 'function'
export function useWebStorage({ storageKey, initialState, storage = 'localStorage' }) {  const store = useSyncExternalStore(    subscribe,    () => getStorageValue({ storageKey, storage }),    () => JSON.stringify(initialState ?? null)  )  const storeRef = useRef(store)  const initialStateRef = useRef(initialState)  const isFirstRender = useRef(true)
  useEffect(() => {    if (      getStorageValue({ storageKey, storage }) === null &&      initialStateRef.current !== undefined &&      isFirstRender.current    ) {      setValueToStorage({ storageKey, storage, value: initialStateRef.current })    }    isFirstRender.current = false  }, [storageKey, storage])
  useEffect(() => {    storeRef.current = store  }, [store])
  const setItemValue = useCallback(    (value) => {      if (value == null) {        throw new TypeError(          `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`        )      }
      let newValue = null
      if (valueIsFunction(value)) {        if (storeRef.current === null && initialStateRef.current === undefined) {          throw new TypeError(            `Cannot call ${value.name} as the store's value is null and no initial state was provided`          )        }        newValue =          storeRef.current !== null            ? value(JSON.parse(storeRef.current))            : value(initialStateRef.current)      } else {        newValue = value      }
      setValueToStorage({ storageKey, storage, value: newValue })    },    [storage, storageKey]  )
  const removeItem = useCallback(() => {    removeValueFromStorage({ storageKey, storage })  }, [storage, storageKey])
  return {    state: JSON.parse(store ?? 'null'),    setItemValue,    removeItem  }}import { useSyncExternalStore, useEffect, useCallback, useRef } from 'react'
export type Storage = 'localStorage' | 'sessionStorage'
const subscribe = (callback: (newValue: string | null) => void) => {  const listener = (event: StorageEvent) => {    callback(event.newValue)  }  window.addEventListener('storage', listener)
  return () => {    window.removeEventListener('storage', listener)  }}
const getStorageValue = ({  storageKey,  storage}: { storageKey: string; storage: Storage }) => {  return window[storage].getItem(storageKey)}
const setValueToStorage = <ValueType>({  storageKey,  storage,  value}: { storageKey: string; storage: Storage; value: ValueType }) => {  const stringyfiedValue = JSON.stringify(value)  window[storage].setItem(storageKey, stringyfiedValue)
  window.dispatchEvent(    new StorageEvent('storage', { key: storageKey, newValue: stringyfiedValue })  )}
const removeValueFromStorage = ({  storageKey,  storage}: { 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>({  storageKey,  initialState,  storage = 'localStorage'}: {  storageKey: string  initialState?: StateType  storage?: Storage}) {  const store = useSyncExternalStore(    subscribe,    () => getStorageValue({ storageKey, storage }),    () => JSON.stringify(initialState ?? null)  )  const storeRef = useRef(store)  const initialStateRef = useRef(initialState)  const isFirstRender = useRef(true)
  useEffect(() => {    if (      getStorageValue({ storageKey, storage }) === null &&      initialStateRef.current !== undefined &&      isFirstRender.current    ) {      setValueToStorage({ storageKey, storage, value: initialStateRef.current })    }    isFirstRender.current = false  }, [storageKey, storage])
  useEffect(() => {    storeRef.current = store  }, [store])
  const setItemValue = useCallback(    (value: StateType | FunctionAsValue<StateType>) => {      if (value == null) {        throw new TypeError(          `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`        )      }
      let newValue = null
      if (valueIsFunction(value)) {        if (storeRef.current === null && initialStateRef.current === undefined) {          throw new TypeError(            `Cannot call ${value.name} as the store's value is null and no initial state was provided`          )        }        newValue =          storeRef.current !== null            ? value(JSON.parse(storeRef.current))            : value(initialStateRef.current as StateType)      } else {        newValue = value      }
      setValueToStorage({ storageKey, storage, value: newValue })    },    [storage, storageKey]  )
  const removeItem = useCallback(() => {    removeValueFromStorage({ storageKey, storage })  }, [storage, storageKey])
  return {    state: JSON.parse(store ?? 'null') as StateType | null,    setItemValue,    removeItem  }}Parameters
- storageKey: A string that represents the key under which the value is stored in the web storage.
- initialState(optional): The initial state value to set if the storage is empty.
- storage(optional): A string that specifies which storage to use, either- 'localStorage'or- 'sessionStorage'. Defaults to- 'localStorage'.
Return
This hook returns an object containing:
- state: The current value stored in the specified storage, parsed as the appropriate type or- nullif not found.
- setItemValue: A function to set a new value in the storage. It can accept a value or a function that returns a new value based on the current state.
- removeItem: A function to remove the item from storage.
Example
import { useWebStorage } from './path/to/use-web-storage'
const WebStorageExample = () => {  const { state, setItemValue, removeItem } = useWebStorage({    storageKey: 'exampleKey',    initialState: 'Hello, World!'  })
  return (    <div>      <h1>Stored Value: {state}</h1>      <button onClick={() => setItemValue('New Value')}>Set New Value</button>      <button onClick={removeItem}>Remove Item</button>    </div>  )}
export default WebStorageExample