DevFiddleDevFiddle
HomeToolsSnippetsArticles
Explore tools
Home>Snippets>useLocalStorage Custom React Hook
React
#Hooks#LocalStorage#State

useLocalStorage Custom React Hook

A robust React hook for syncing component state with window.localStorage across tabs.

October 10, 20263 min read

Persisting state to localStorage while ensuring compatibility with Next.js SSR hydration is a common requirement in modern web applications.

The Hook Implementation

import { useState, useEffect } from "react";
 
export function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
  // Read initial stored value safely on the client
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === "undefined") {
      return initialValue;
    }
    try {
      const item = window.localStorage.getItem(key);
      return item ? (JSON.parse(item) as T) : initialValue;
    } catch (error) {
      console.warn(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  });
 
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      if (typeof window !== "undefined") {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.warn(`Error setting localStorage key "${key}":`, error);
    }
  };
 
  // Sync state if localStorage changes in another browser tab
  useEffect(() => {
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === key && e.newValue) {
        try {
          setStoredValue(JSON.parse(e.newValue) as T);
        } catch {
          // Ignore parse errors
        }
      }
    };
 
    window.addEventListener("storage", handleStorageChange);
    return () => window.removeEventListener("storage", handleStorageChange);
  }, [key]);
 
  return [storedValue, setValue];
}

Example Usage

function ThemePreference() {
  const [theme, setTheme] = useLocalStorage<"light" | "dark">("app-theme", "light");
 
  return (
    <button
      onClick={() => setTheme((prev) => (prev === "light" ? "dark" : "light"))}
    >
      Current Theme: {theme}
    </button>
  );
}

Related snippets

Explore more utility functions, patterns, and code snippets.

View all snippets
React
useClickOutside React Hook

Detect clicks outside a designated element to dismiss dropdowns, modals, and popovers smoothly.

#React#Hooks#DOM
View snippet
TypeScript
Debounce Function with TypeScript

A type-safe debounce implementation that delays executing a function until a specified wait time has elapsed.

#TypeScript#Utilities#Performance
View snippet
JavaScript
Deep Clone Objects in Modern JavaScript

Deeply copy nested JavaScript objects and arrays using native structuredClone and fallback recursion.

#JavaScript#Objects#Data Structures
View snippet
About© DevFiddle 2026