Open Hooks
State Management

useLocalStorage

Persist and sync state with localStorage for reliable client-side storage in React.

useLocalStorage

Persist and sync state with localStorage for reliable client-side storage in React.

Status: stable
Version: 2.0.0
Bundle Size: ~0.7kb
Requires: React 16.8+

useLocalStorage Demo

Your name is saved in localStorage and will persist across page reloads.

Installation

npx open-hook add useLocalStorage
yarn dlx open-hook add useLocalStorage
pnpm dlx open-hook add useLocalStorage

API Reference

Parameters

PropTypeDefault
initialValue?
T
-
key?
string
-

Returns

PropTypeDefault
setValue?
(value: T | ((val: T) => T)) => void
-
value?
T
-

TypeScript Signature

function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T | ((val: T) => T)) => void];

Advanced Usage Examples

// Simple string value
const [username, setUsername] = useLocalStorage("username", "");

// Boolean preference
const [darkMode, setDarkMode] = useLocalStorage("darkMode", false);

// Number setting
const [fontSize, setFontSize] = useLocalStorage("fontSize", 16);
interface UserProfile {
  name: string;
  email: string;
  preferences: {
    theme: string;
    language: string;
  };
}

const [profile, setProfile] = useLocalStorage<UserProfile>("userProfile", {
  name: "",
  email: "",
  preferences: {
    theme: "light",
    language: "en",
  },
});

// Update nested properties
const updateTheme = (newTheme: string) => {
  setProfile((prev) => ({
    ...prev,
    preferences: {
      ...prev.preferences,
      theme: newTheme,
    },
  }));
};
interface FormData {
  title: string;
  content: string;
  tags: string[];
}

function DraftEditor() {
  const [formData, setFormData] = useLocalStorage<FormData>("draft", {
    title: "",
    content: "",
    tags: [],
  });

  const handleSubmit = () => {
    // Submit form
    submitPost(formData);

    // Clear draft after successful submission
    setFormData({
      title: "",
      content: "",
      tags: [],
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={formData.title}
        onChange={(e) =>
          setFormData((prev) => ({
            ...prev,
            title: e.target.value,
          }))
        }
        placeholder="Title"
      />
      {/* More form fields */}
    </form>
  );
}
function useLocalStorageWithSync<T>(key: string, initialValue: T) {
  const [value, setValue] = useLocalStorage(key, initialValue);

  useEffect(() => {
    const handleStorageChange = (e: StorageEvent) => {
      if (e.key === key && e.newValue !== null) {
        setValue(JSON.parse(e.newValue));
      }
    };

    window.addEventListener("storage", handleStorageChange);
    return () => window.removeEventListener("storage", handleStorageChange);
  }, [key, setValue]);

  return [value, setValue];
}

Best Practices

Choose appropriate storage strategies based on your use case:

// Theme preferences - simple and persistent
const [theme, setTheme] = useLocalStorage("app-theme", "light");

// User settings - complex object with defaults
const [settings, setSettings] = useLocalStorage("user-settings", {
  notifications: true,
  autoSave: true,
  language: "en",
});

// Form drafts - auto-save with cleanup
const [draft, setDraft] = useLocalStorage("post-draft", {
  title: "",
  content: "",
});

Security & Performance Guidelines

  • 🔒 Data Security: Never store sensitive data like passwords or tokens
  • 📦 Size Limits: Keep data under 5MB (localStorage limit varies by browser)
  • 🚀 Performance: Use for small to medium-sized data only
  • 🔄 Serialization: Ensure data is JSON-serializable

Do's and Don'ts

  • ✅ Use for user preferences and settings
  • ✅ Store theme, language, and UI state
  • ✅ Implement form draft functionality
  • ✅ Cache non-sensitive application data
  • ❌ Don't store passwords or authentication tokens
  • ❌ Avoid storing large files or images
  • ❌ Don't rely on localStorage for critical application state
  • ❌ Avoid circular references in objects

Performance Metrics

MetricValueDescription
Bundle Size~0.7kbMinified + gzipped
PersistencePermanentSurvives browser restarts
Sync SpeedInstantImmediate localStorage updates
Storage Limit~5-10MBBrowser-dependent limit

Browser Compatibility

BrowserSupportedNotes
Chrome (latest)YesFull support
Firefox (latest)YesFull support
Safari (latest)YesFull support
Edge (latest)YesFull support
IE 11YesBasic support
Node.jsNoSSR-safe with fallback

Use Cases

Theme & Preferences

Store user interface preferences like theme, language, and layout settings.

Form Draft Saving

Automatically save form data to prevent data loss on page refresh.

Shopping Cart Persistence

Maintain shopping cart contents across browser sessions.

Application Settings

Store non-sensitive application configuration and user customizations.

Accessibility

Accessibility Considerations

  • localStorage doesn't directly impact accessibility - Use for storing accessibility preferences (font size, contrast settings) - Ensure UI updates from localStorage are announced to screen readers - Consider high contrast and reduced motion preferences

Troubleshooting

Internals

How It Works

The hook initializes state by reading from localStorage on mount, parsing the JSON value, and falling back to the initial value if parsing fails or localStorage is unavailable. When the value changes, it immediately updates both React state and localStorage using JSON.stringify().

The hook includes SSR safety by checking for window availability and graceful error handling for localStorage quota exceeded or parsing errors.

Testing Example

import { renderHook, act } from "@testing-library/react";
import { useLocalStorage } from "./useLocalStorage";

// Mock localStorage
const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  removeItem: jest.fn(),
  clear: jest.fn(),
};
global.localStorage = localStorageMock as any;

describe("useLocalStorage", () => {
  beforeEach(() => {
    localStorageMock.getItem.mockClear();
    localStorageMock.setItem.mockClear();
  });

  it("should return initial value when localStorage is empty", () => {
    localStorageMock.getItem.mockReturnValue(null);
    const { result } = renderHook(() => useLocalStorage("test-key", "default"));

    expect(result.current[0]).toBe("default");
  });

  it("should persist value to localStorage", () => {
    const { result } = renderHook(() => useLocalStorage("test-key", "default"));

    act(() => {
      result.current[1]("new-value");
    });

    expect(localStorageMock.setItem).toHaveBeenCalledWith(
      "test-key",
      JSON.stringify("new-value")
    );
    expect(result.current[0]).toBe("new-value");
  });

  it("should handle function updates", () => {
    const { result } = renderHook(() => useLocalStorage("test-key", 0));

    act(() => {
      result.current[1]((prev) => prev + 1);
    });

    expect(result.current[0]).toBe(1);
  });
});

FAQ

Changelog

  • 2.0.0 — Enhanced TypeScript support, added functional updates, improved SSR handling, better error handling, and comprehensive documentation
  • 1.2.0 — Added support for complex objects and arrays with better JSON serialization
  • 1.1.0 — Improved error handling and SSR compatibility
  • 1.0.0 — Initial release with basic localStorage persistence functionality