Skip to content

useReadLocalStorage

Custom hook that reads a value from localStorage, closely related to useLocalStorage().

Usage

tsx
import { useReadLocalStorage } from 'vhooks'

export default function Component() {
  // Assuming a value was set in localStorage with this key
  const darkMode = useReadLocalStorage('darkMode')

  return <p>DarkMode is {darkMode ? 'enabled' : 'disabled'}</p>
}

Installation

sh
pnpm dlx scaflo@latest https://raw.githubusercontent.com/programming-with-ia/vDocs/scaflos/hooks/useReadLocalStorage.json -e %src%/hooks
sh
npx scaflo@latest https://raw.githubusercontent.com/programming-with-ia/vDocs/scaflos/hooks/useReadLocalStorage.json -e %src%/hooks
sh
bunx scaflo@latest https://raw.githubusercontent.com/programming-with-ia/vDocs/scaflos/hooks/useReadLocalStorage.json -e %src%/hooks
sh
yarn dlx scaflo@latest https://raw.githubusercontent.com/programming-with-ia/vDocs/scaflos/hooks/useReadLocalStorage.json -e %src%/hooks

API

useReadLocalStorage<T>(key, options): T | null | undefined

Custom hook that reads a value from localStorage, closely related to useLocalStorage().

Type parameters

NameDescription
TThe type of the stored value.

Parameters

NameTypeDescription
keystringThe key associated with the value in local storage.
optionsOptions<T, false>Additional options for reading the value (optional).

Returns

T | null | undefined

The stored value, or null if the key is not present or an error occurs.

useReadLocalStorage<T>(key, options?): T | null

Custom hook that reads a value from localStorage, closely related to useLocalStorage().

Type parameters

NameDescription
TThe type of the stored value.

Parameters

NameTypeDescription
keystringThe key associated with the value in local storage.
options?Partial<Options<T, true>>Additional options for reading the value (optional).

Returns

T | null

The stored value, or null if the key is not present or an error occurs.

Type aliases

Ƭ Options<T, InitializeWithValue>: Object

Represents the type for the options available when reading from local storage.

Type parameters

NameTypeDescription
TTThe type of the stored value.
InitializeWithValueextends boolean | undefined-

Type declaration

NameTypeDescription
deserializer?(value: string) => T-
initializeWithValueInitializeWithValueIf true (default), the hook will initialize reading the local storage. In SSR, you should set it to false, returning undefined initially.

Hook

ts
import { useCallback, useEffect, useState } from 'react'

import { useEventListener } from 'vhooks'

const IS_SERVER = typeof window === 'undefined'

type Options<T, InitializeWithValue extends boolean | undefined> = {
    deserializer?: (value: string) => T
    initializeWithValue: InitializeWithValue
}

// SSR version
export function useReadLocalStorage<T>(
  key: string,
  options: Options<T, false>,
): T | null | undefined
// CSR version
export function useReadLocalStorage<T>(
  key: string,
  options?: Partial<Options<T, true>>,
): T | null
export function useReadLocalStorage<T>(
  key: string,
  options: Partial<Options<T, boolean>> = {},
): T | null | undefined {
  let { initializeWithValue = true } = options
  if (IS_SERVER) {
    initializeWithValue = false
  }

  const deserializer = useCallback<(value: string) => T | null>(
    value => {
      if (options.deserializer) {
        return options.deserializer(value)
      }
      // Support 'undefined' as a value
      if (value === 'undefined') {
        return undefined as unknown as T
      }

      let parsed: unknown
      try {
        parsed = JSON.parse(value)
      } catch (error) {
        console.error('Error parsing JSON:', error)
        return null
      }

      return parsed as T
    },
    [options],
  )

  // Get from local storage then
  // parse stored json or return initialValue
  const readValue = useCallback((): T | null => {
    // Prevent build error "window is undefined" but keep keep working
    if (IS_SERVER) {
      return null
    }

    try {
      const raw = window.localStorage.getItem(key)
      return raw ? deserializer(raw) : null
    } catch (error) {
      console.warn(`Error reading localStorage key “${key}”:`, error)
      return null
    }
  }, [key, deserializer])

  const [storedValue, setStoredValue] = useState(() => {
    if (initializeWithValue) {
      return readValue()
    }
    return undefined
  })

  // Listen if localStorage changes
  useEffect(() => {
    setStoredValue(readValue())
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [key])

  const handleStorageChange = useCallback(
    (event: StorageEvent | CustomEvent) => {
      if ((event as StorageEvent).key && (event as StorageEvent).key !== key) {
        return
      }
      setStoredValue(readValue())
    },
    [key, readValue],
  )

  // this only works for other documents, not the current one
  useEventListener('storage', handleStorageChange)

  // this is a custom event, triggered in writeValueToLocalStorage
  // See: useLocalStorage()
  useEventListener('local-storage', handleStorageChange)

  return storedValue
}

vDocs