useLocalStorage

A custom React hook that persists state in localStorage and syncs across browser tabs.

  • Allows data persistence between page refreshes and browser sessions
  • Automatically syncs state across browser tabs with the same site open
  • Uses the same API as React's useState hook for a familiar developer experience

Installation

The useLocalStorage hook is part of our UI library. You can import it directly from the hooks directory.


Usage

Here is how to use the useLocalStorage hook in your project:

React TSX
1import { useLocalStorage } from "@/hooks/use-local-storage"

React TSX
1function Demo() {
2  // Similar API to useState
3  const [value, setValue] = useLocalStorage("storage-key", "default value")
4  
5  return (
6    <div>
7      <p>Current value: {value}</p>
8      <button onClick={() => setValue("new value")}>Update value</button>
9      <button onClick={() => setValue(prev => `${prev}-updated`)}>Update functionally</button>
10    </div>
11  )
12}

API Reference

React TSX
1function useLocalStorage<T>(
2  key: string,
3  initialValue: T
4): [T, (value: T | ((val: T) => T)) => void]

Inputs

  • key: The key to store the value under in localStorage
  • initialValue: The initial value to use if no value is found in localStorage

Output

  • value: The current state value
  • setValue: Function to update the state (works like useState setter)

Behavior

  • First render retrieves the value from localStorage (or uses initialValue if not found)
  • Any updates through the setter will persist to localStorage
  • Changes to localStorage from other tabs or windows will update the state automatically
  • The hook handles serialization to JSON for storage and parsing on retrieval

Implementation

React TSX
1import { useState, useEffect } from "react"
2
3export function useLocalStorage<T>(key: string, initialValue: T) {
4  // Get from local storage then
5  // parse stored json or return initialValue
6  const readValue = () => {
7    // Prevent build error "window is undefined" but keep working
8    if (typeof window === "undefined") {
9      return initialValue
10    }
11
12    try {
13      const item = window.localStorage.getItem(key)
14      return item ? JSON.parse(item) : initialValue
15    } catch (error) {
16      console.warn(`Error reading localStorage key "${key}":`, error)
17      return initialValue
18    }
19  }
20
21  // State to store our value
22  // Pass initial state function to useState so logic is only executed once
23  const [storedValue, setStoredValue] = useState<T>(readValue)
24
25  // Return a wrapped version of useState's setter function that ...
26  // ... persists the new value to localStorage.
27  const setValue = (value: T | ((val: T) => T)) => {
28    try {
29      // Allow value to be a function so we have the same API as useState
30      const valueToStore =
31        value instanceof Function ? value(storedValue) : value
32      
33      // Save state
34      setStoredValue(valueToStore)
35      
36      // Save to local storage
37      if (typeof window !== "undefined") {
38        window.localStorage.setItem(key, JSON.stringify(valueToStore))
39      }
40    } catch (error) {
41      console.warn(`Error setting localStorage key "${key}":`, error)
42    }
43  }
44
45  useEffect(() => {
46    const handleStorageChange = (event: StorageEvent) => {
47      if (event.key === key && event.newValue) {
48        setStoredValue(JSON.parse(event.newValue))
49      }
50    }
51
52    // Listen for changes to this local storage value made from other windows
53    window.addEventListener("storage", handleStorageChange)
54    
55    return () => {
56      window.removeEventListener("storage", handleStorageChange)
57    }
58  }, [key])
59
60  return [storedValue, setValue] as const
61}