A lightweight, type-safe, and reactive state management library for modern web applications
- 🎯 Simple & Intuitive API - Get started in minutes with a familiar localStorage-like API
- 💾 Multiple Storage Strategies - Choose between localStorage, sessionStorage, cookies, or in-memory storage
- 📦 TypeScript First - Full TypeScript support with intelligent type inference
- 🔄 Reactive Updates - Built-in observer pattern for real-time state synchronization
- 🛡️ Type Safety - Automatic type casting and validation with custom error handling
- 🪶 Lightweight - Minimal bundle size (10kb)
- 🔧 Flexible Configuration - Fallback values, strict mode, and custom casting options
- 🍪 Advanced Cookie Support - Full cookie options including SameSite, Secure, and expiration
npm install @sovgut/state
yarn add @sovgut/state
pnpm add @sovgut/state
import { LocalState, SessionState, MemoryState, CookieState } from "@sovgut/state";
// Store a value
LocalState.set("user", { name: "John", age: 30 });
// Retrieve a value
const user = LocalState.get("user");
// Listen for changes
LocalState.on("user", (event) => {
console.log("User updated:", event);
});
- Storage Strategies
- Basic Usage
- Type Safety & Casting
- Observer Pattern
- React Integration
- API Reference
- Error Handling
Persists data in localStorage
- survives browser restarts.
import { LocalState } from "@sovgut/state";
LocalState.set("theme", "dark");
const theme = LocalState.get("theme"); // "dark"
Stores data in sessionStorage
- cleared when tab closes.
import { SessionState } from "@sovgut/state";
SessionState.set("tempData", { expires: Date.now() + 3600000 });
const data = SessionState.get("tempData");
In-memory storage - cleared on page reload.
import { MemoryState } from "@sovgut/state";
MemoryState.set("cache", new Map([["key", "value"]]));
const cache = MemoryState.get("cache");
HTTP cookie storage with advanced options.
import { CookieState } from "@sovgut/state";
CookieState.set("sessionId", "abc123", {
expires: 7, // days
secure: true,
sameSite: "strict"
});
All storage types support any serializable JavaScript value:
// Primitives
LocalState.set("count", 42);
LocalState.set("name", "Alice");
LocalState.set("isActive", true);
LocalState.set("bigNumber", 9007199254740991n);
// Objects and Arrays
LocalState.set("user", { id: 1, name: "Bob" });
LocalState.set("tags", ["javascript", "typescript"]);
// Complex structures
LocalState.set("settings", {
theme: "dark",
notifications: {
email: true,
push: false
},
favorites: ["dashboard", "profile"]
});
const count = LocalState.get("count"); // 42
const user = LocalState.get("user"); // { id: 1, name: "Bob" }
const missing = LocalState.get("nonexistent"); // undefined
// Returns fallback if key doesn't exist
const theme = LocalState.get("theme", { fallback: "light" }); // "light"
// Fallback also determines the return type
const score = LocalState.get("score", { fallback: 0 }); // number
const tags = LocalState.get("tags", { fallback: [] as string[] }); // string[]
// Throws StateDoesNotExist error if key is missing
try {
const required = LocalState.get("required", { strict: true });
} catch (error) {
console.error("Key does not exist:", error.message);
}
// Remove single item
LocalState.remove("user");
// Clear all items
LocalState.clear();
// Check existence
if (LocalState.has("user")) {
LocalState.remove("user");
}
// TypeScript infers types from fallback values
const count = LocalState.get("count", { fallback: 0 }); // number
const name = LocalState.get("name", { fallback: "" }); // string
const items = LocalState.get("items", { fallback: [] as Item[] }); // Item[]
// Cast stored values to specific types
LocalState.set("stringNumber", "42");
const asString = LocalState.get("stringNumber", { cast: "string" }); // "42"
const asNumber = LocalState.get("stringNumber", { cast: "number" }); // 42
const asBoolean = LocalState.get("stringNumber", { cast: "boolean" }); // true
const asBigInt = LocalState.get("stringNumber", { cast: "bigint" }); // 42n
interface User {
id: number;
name: string;
email: string;
}
// Explicitly type the return value
const user = LocalState.get<User>("currentUser");
// With strict mode
const user = LocalState.get<User>("currentUser", { strict: true });
import { type IStorageEventData } from "@sovgut/state";
// Listen for all changes to a key
LocalState.on("user", (event: IStorageEventData) => {
console.log("User changed:", event);
});
// Listen once
LocalState.once("notification", (event) => {
console.log("Notification received:", event);
});
// Remove specific listener
const handler = (event: IStorageEventData) => console.log(event);
LocalState.on("data", handler);
LocalState.off("data", handler);
// Remove all listeners
LocalState.removeAllListeners();
// Component A
LocalState.set("sharedState", { count: 1 });
// Component B - automatically receives updates
LocalState.on("sharedState", (event) => {
console.log("State updated in Component A:", event);
});
import { useEffect, useState, useCallback } from "react";
import { LocalState, type IStorageEventData } from "@sovgut/state";
function useLocalState<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() =>
LocalState.get(key, { fallback: initialValue })
);
useEffect(() => {
const handler = (event: IStorageEventData<T>) => {
setValue(event ?? initialValue);
};
LocalState.on(key, handler);
return () => LocalState.off(key, handler);
}, [key, initialValue]);
const updateValue = useCallback((newValue: T) => {
LocalState.set(key, newValue);
}, [key]);
return [value, updateValue] as const;
}
// Usage
function App() {
const [theme, setTheme] = useLocalState("theme", "light");
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Current theme: {theme}
</button>
);
}
import React, { createContext, useContext, useEffect, useState } from "react";
import { LocalState, type IStorageEventData } from "@sovgut/state";
interface AppState {
user: User | null;
settings: Settings;
}
const StateContext = createContext<{
state: AppState;
updateUser: (user: User | null) => void;
updateSettings: (settings: Settings) => void;
}>({} as any);
export function StateProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState<AppState>({
user: LocalState.get("user", { fallback: null }),
settings: LocalState.get("settings", { fallback: defaultSettings })
});
useEffect(() => {
const handleUserChange = (event: IStorageEventData<User>) => {
setState(prev => ({ ...prev, user: event }));
};
const handleSettingsChange = (event: IStorageEventData<Settings>) => {
setState(prev => ({ ...prev, settings: event ?? defaultSettings }));
};
LocalState.on("user", handleUserChange);
LocalState.on("settings", handleSettingsChange);
return () => {
LocalState.off("user", handleUserChange);
LocalState.off("settings", handleSettingsChange);
};
}, []);
const updateUser = (user: User | null) => LocalState.set("user", user);
const updateSettings = (settings: Settings) => LocalState.set("settings", settings);
return (
<StateContext.Provider value={{ state, updateUser, updateSettings }}>
{children}
</StateContext.Provider>
);
}
export const useAppState = () => useContext(StateContext);
All storage classes (LocalState
, SessionState
, MemoryState
, CookieState
) share these methods:
Retrieves a value from storage.
Options:
-
fallback?: T
- Default value if key doesn't exist -
strict?: boolean
- Throw error if key doesn't exist -
cast?: 'string' | 'number' | 'boolean' | 'bigint'
- Type casting
Stores a value in storage.
Cookie-specific options:
-
expires?: Date | number
- Expiration date or days from now -
maxAge?: number
- Maximum age in seconds -
domain?: string
- Cookie domain -
path?: string
- Cookie path -
secure?: boolean
- HTTPS only -
sameSite?: 'strict' | 'lax' | 'none'
- CSRF protection
Removes a value from storage.
Removes all values from storage.
Checks if a key exists in storage.
Adds an event listener.
Adds a one-time event listener.
Removes an event listener.
Removes all event listeners.
import { StateDoesNotExist, StateInvalidCast } from "@sovgut/state";
// Handle missing keys
try {
const data = LocalState.get("required", { strict: true });
} catch (error) {
if (error instanceof StateDoesNotExist) {
console.error(`Key "${error.key}" not found in ${error.storage}`);
}
}
// Handle invalid casts
try {
LocalState.set("invalid", "not-a-number");
const num = LocalState.get("invalid", { cast: "number", strict: true });
} catch (error) {
if (error instanceof StateInvalidCast) {
console.error(`Cannot cast "${error.value}" to ${error.type} for key "${error.key}" in ${error.storage}`);
}
}
If you find any issues or have suggestions for improvements, feel free to open an issue or submit a pull request on GitHub.
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ by sovgut
GitHub • npm • Documentation