useLocalStorage

React hook to store, retrieve and delete data from the browser's localStorage API.

Add the hook via the CLI:

npx @novajslabs/cli add useLocalStorage

Or copy and paste the code into your project:

import { useEffect, useSyncExternalStore, useCallback } from 'react'

const isFunction = <T>(
  value: T | ((prevState: T) => T),
): value is (prevState: T) => T => typeof value === 'function'

const dispatchStorageEvent = (key: string, newValue: string | null) =>
  window.dispatchEvent(new StorageEvent('storage', { key, newValue }))

const getLocalStorageItem = (key: string) => window.localStorage.getItem(key)

const setLocalStorageItem = <T>(key: string, value: T) => {
  const stringifiedValue = JSON.stringify(value)
  window.localStorage.setItem(key, stringifiedValue)
  dispatchStorageEvent(key, stringifiedValue)
}

const removeLocalStorageItem = (key: string) => {
  window.localStorage.removeItem(key)
  dispatchStorageEvent(key, null)
}

const localStorageSubscribe = (cb: () => void) => {
  window.addEventListener('storage', cb)
  return () => window.removeEventListener('storage', cb)
}

export const useLocalStorage = <T>(key: string, initialValue: T) => {
  const getSnapshot = () => getLocalStorageItem(key)
  const store = useSyncExternalStore(localStorageSubscribe, getSnapshot)

  const setState = useCallback(
    (v: T) => {
      try {
        let nextState: T
        if (isFunction(v)) {
          const parsedStore = store ? JSON.parse(store) : null
          nextState = v(parsedStore ?? initialValue)
        } else {
          nextState = v
        }

        if (nextState === undefined || nextState === null) {
          removeLocalStorageItem(key)
        } else {
          setLocalStorageItem(key, nextState)
        }
      } catch (e) {
        console.log(e)
      }
    },
    [key, store, initialValue],
  )

  useEffect(() => {
    if (
      getLocalStorageItem(key) === null &&
      typeof initialValue !== 'undefined'
    ) {
      setLocalStorageItem(key, initialValue)
    }
  }, [key, initialValue])

  return {
    current: store ? JSON.parse(store) : initialValue,
    setItemValue: setState,
    removeItem: () => removeLocalStorageItem(key),
  }
}

React 18.0 or higher

  • Name
    key
    Type
    string
    Required
    required
    Description

    The key used to get to the local storage value.

  • Name
    initialValue
    Type
    any
    Required
    required
    Description

    The initial value to use if there is no item in local storage with the given key.

  • Name
    current
    Type
    any
    Description

    The current value of the item in the local storage.

  • Name
    setItemValue
    Type
    function
    Description

    Set the item value to local storage.

  • Name
    removeItem
    Type
    function
    Description

    Remove the item from local storage.

import { useLocalStorage } from './hooks/useLocalStorage'

const App = () => {
  const { current, setItemValue, removeItem } = useLocalStorage<number>(
    'number',
    0,
  )

  return (
    <div>
      <p>Current value: {current}</p>
      <button onClick={() => setItemValue(Math.floor(Math.random() * 11))}>
        Generate new number
      </button>
      <button onClick={removeItem}>Delete "number" item</button>
    </div>
  )
}

export default App

Here are some use cases where this React hook is useful:

  • Storing and retrieving user preferences (e.g., theme selection, language choice) in a web application
  • Implementing persistent state management for user-generated content (e.g., notes, bookmarks) in a browser-based productivity tool
  • Saving and restoring user settings (e.g., font size, color scheme) across sessions in a web-based reading application
  • Enabling data persistence for form input values in a multi-step form or form builder tool
  • Creating a customizable user experience by allowing users to save and recall their personalized configurations in a web app