npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@lucasriondel/use-local-storage-reacthook

v0.0.1

Published

A React hook for managing local storage

Readme

💾 useLocalStorageState

A powerful React hook for managing state that's automatically persisted to localStorage. Perfect for maintaining user preferences, form data, and application settings across browser sessions with automatic synchronization and data migration support.

TypeScript MIT License React

✨ Features

  • 🎯 Type-safe - Full TypeScript support with generic types
  • 💾 Automatic localStorage sync - State changes are persisted instantly
  • 🔄 Cross-tab synchronization - Share state changes across browser tabs
  • 🛡️ Validation - Built-in sanitization and validation hooks
  • 📦 Custom serialization - Define custom codecs for complex data types
  • 🔄 Data migration - Handle schema changes with version migration
  • Performance optimized - Efficient serialization and change detection
  • 🪶 Lightweight - Zero dependencies (except React peer dependency)

📦 Installation

# Using npm
npm install @lucasriondel/use-local-storage-reacthook

# Using yarn
yarn add @lucasriondel/use-local-storage-reacthook

# Using pnpm
pnpm add @lucasriondel/use-local-storage-reacthook

🚀 Quick Start

import { useLocalStorageState } from "@lucasriondel/use-local-storage-reacthook";

function UserPreferences() {
  const [settings, settingsApi] = useLocalStorageState(
    {
      theme: "light",
      language: "en",
      notifications: true,
    },
    { key: "user-preferences" }
  );

  return (
    <div>
      <select
        value={settings.theme}
        onChange={(e) => settingsApi.set("theme", e.target.value)}
      >
        <option value="light">Light Theme</option>
        <option value="dark">Dark Theme</option>
      </select>

      <select
        value={settings.language}
        onChange={(e) => settingsApi.set("language", e.target.value)}
      >
        <option value="en">English</option>
        <option value="fr">French</option>
        <option value="es">Spanish</option>
      </select>

      <label>
        <input
          type="checkbox"
          checked={settings.notifications}
          onChange={(e) => settingsApi.set("notifications", e.target.checked)}
        />
        Enable Notifications
      </label>
    </div>
  );
}

Your settings will automatically persist across browser sessions!

📚 API Reference

useLocalStorageState(defaults, options)

Returns a tuple [state, api] where:

  • state: The current state object
  • api: Object with methods to manipulate the state

Parameters

| Parameter | Type | Description | | ---------- | ------------------------------------------ | ---------------------------- | | defaults | DeepPartial<T> \| (() => DeepPartial<T>) | Default values for the state | | options | LocalStorageStateOptions<T> | Configuration options |

Options

| Option | Type | Default | Description | | ---------------- | -------------------------------------------------- | ----------- | ------------------------------------------------- | | key | string | Required | The localStorage key to store data under | | codecs | Partial<{ [K in keyof T]: Codec<T[K]> }> | {} | Custom serialization for specific properties | | sanitize | (draft: DeepPartial<T>) => DeepPartial<T> | undefined | Validation/sanitization function | | onChange | (state: T, meta) => void | undefined | Callback fired on state changes | | syncAcrossTabs | boolean | true | Sync state changes across browser tabs | | version | number | undefined | Version number for data migration | | migrate | (stored: unknown, version: number) => Partial<T> | undefined | Function to migrate old data when version changes |

API Methods

| Method | Signature | Description | | ---------- | ---------------------------------------------------- | ---------------------------------------------- | | setState | (updater: T \| (prev: T) => T) => void | Replace entire state | | get | (key: keyof T) => T[key] \| undefined | Get value of specific property | | set | (key: keyof T, value: T[key] \| undefined) => void | Set specific property (or delete if undefined) | | patch | (partial: DeepPartial<T>) => void | Merge partial changes | | remove | (...keys: (keyof T)[]) => void | Remove one or more properties | | clear | () => void | Clear all data from localStorage |

🎯 Examples

Basic Usage

import { useLocalStorageState } from "use-local-storage-reacthook";

function App() {
  const [profile, profileApi] = useLocalStorageState(
    { name: "", age: 0 },
    { key: "user-profile" }
  );

  return (
    <div>
      <input
        value={profile.name}
        onChange={(e) => profileApi.set("name", e.target.value)}
        placeholder="Enter your name"
      />
      <input
        type="number"
        value={profile.age}
        onChange={(e) => profileApi.set("age", parseInt(e.target.value) || 0)}
        placeholder="Enter your age"
      />
      <button onClick={() => profileApi.clear()}>Clear Profile</button>
    </div>
  );
}

With Custom Serialization

interface AppSettings {
  tags: string[];
  lastLoginDate: Date;
  preferences: { theme: string; lang: string };
}

const [settings, settingsApi] = useLocalStorageState<AppSettings>(
  {
    tags: [],
    lastLoginDate: new Date(),
    preferences: { theme: "light", lang: "en" },
  },
  {
    key: "app-settings",
    codecs: {
      tags: {
        parse: (str) => str.split(",").filter(Boolean),
        format: (tags) => tags.join(","),
      },
      lastLoginDate: {
        parse: (str) => new Date(str),
        format: (date) => date.toISOString(),
      },
    },
  }
);

With Validation and Change Tracking

const [userPrefs, prefsApi] = useLocalStorageState(
  {
    theme: "light",
    fontSize: 16,
    language: "en",
  },
  {
    key: "user-preferences",
    sanitize: (draft) => ({
      theme: ["light", "dark"].includes(draft.theme) ? draft.theme : "light",
      fontSize: Math.max(12, Math.min(24, draft.fontSize || 16)),
      language: ["en", "fr", "es"].includes(draft.language)
        ? draft.language
        : "en",
    }),
    onChange: (newState, { source }) => {
      console.log(`Preferences updated from ${source}:`, newState);
      // Send analytics, trigger theme updates, etc.
    },
  }
);

Multiple Hook Instances with Different Keys

function Dashboard() {
  // User filters stored under 'user-filters' key
  const [userFilters, userApi] = useLocalStorageState(
    {
      role: "all",
      department: "all",
    },
    { key: "user-filters" }
  );

  // Product filters stored under 'product-filters' key
  const [productFilters, productApi] = useLocalStorageState(
    {
      category: "all",
      inStock: true,
    },
    { key: "product-filters" }
  );

  // Each hook manages its own localStorage entry independently
  // localStorage: { "user-filters": {...}, "product-filters": {...} }
}

Complex State Management with Data Migration

interface AppState {
  filters: {
    search: string;
    category: string[];
    priceRange: [number, number];
  };
  view: "grid" | "list";
  sort: { field: string; direction: "asc" | "desc" };
}

const [appState, api] = useLocalStorageState<AppState>(
  {
    filters: {
      search: "",
      category: [],
      priceRange: [0, 1000],
    },
    view: "grid",
    sort: { field: "name", direction: "asc" },
  },
  {
    key: "app-state",
    version: 2,
    migrate: (stored, version) => {
      if (version < 2) {
        // Migrate from v1: add new priceRange field
        const oldState = stored as any;
        return {
          ...oldState,
          filters: {
            ...oldState.filters,
            priceRange: [0, 1000], // Add default price range
          },
        };
      }
      return stored as Partial<AppState>;
    },
  }
);

// Update nested properties
api.patch({
  filters: {
    ...appState.filters,
    search: "new search term",
  },
});

// Toggle sort direction
api.set("sort", {
  ...appState.sort,
  direction: appState.sort.direction === "asc" ? "desc" : "asc",
});

🔧 Advanced Configuration

Cross-Tab Synchronization

// Enable cross-tab sync (default)
const [state, api] = useLocalStorageState(defaults, {
  key: "shared-state",
  syncAcrossTabs: true,
});

// Disable cross-tab sync for performance or privacy
const [state, api] = useLocalStorageState(defaults, {
  key: "local-only-state",
  syncAcrossTabs: false,
});

Data Migration Between Versions

const [config, configApi] = useLocalStorageState(
  { apiUrl: "https://api.example.com", timeout: 5000 },
  {
    key: "app-config",
    version: 3,
    migrate: (stored, currentVersion) => {
      const data = stored as any;

      if (currentVersion < 2) {
        // v1 -> v2: rename 'endpoint' to 'apiUrl'
        data.apiUrl = data.endpoint;
        delete data.endpoint;
      }

      if (currentVersion < 3) {
        // v2 -> v3: add timeout field
        data.timeout = data.timeout || 5000;
      }

      return data;
    },
  }
);

Error Handling and Validation

const [userInput, inputApi] = useLocalStorageState(
  { email: "", age: 0 },
  {
    key: "user-input",
    sanitize: (draft) => {
      // Validate and sanitize data from localStorage
      const email = typeof draft.email === "string" ? draft.email : "";
      const age =
        typeof draft.age === "number" && draft.age >= 0 ? draft.age : 0;

      return { email, age };
    },
    onChange: (newState, { source }) => {
      if (source === "external") {
        console.log("State updated from another tab:", newState);
      }
    },
  }
);

📝 TypeScript Support

The hook is fully typed and provides excellent TypeScript integration:

interface UserProfile {
  name: string;
  roles: ("admin" | "user" | "guest")[];
  isActive: boolean;
  metadata?: { lastLogin: Date };
}

// Full type safety
const [profile, profileApi] = useLocalStorageState<UserProfile>(
  {
    name: "",
    roles: [],
    isActive: true,
  },
  { key: "user-profile" }
);

// TypeScript knows the exact shape
profileApi.set("name", "john"); // ✅ Valid
profileApi.set("roles", ["admin", "user"]); // ✅ Valid
profileApi.set("invalidProp", "value"); // ❌ TypeScript error

// Partial updates are also type-safe
profileApi.patch({
  name: "jane",
  isActive: false,
}); // ✅ Valid

profileApi.patch({
  invalidField: true,
}); // ❌ TypeScript error

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Inspired by the need for better localStorage state management in React applications
  • Built with TypeScript for maximum developer experience
  • Designed to handle complex state persistence scenarios with ease
  • Supports modern React patterns and best practices

Happy coding! 🚀 If you find this hook useful, please consider giving it a ⭐ on GitHub!