UNPKG

state-manager-utility

Version:
493 lines (385 loc) • 12.5 kB
# State Manager Utility [![npm version](https://img.shields.io/npm/v/state-manager-utility.svg)](https://www.npmjs.com/package/state-manager-utility) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) A tiny yet powerful state management library for front-end projects. Follows pub-sub architecture to maintain and broadcast state. Can be used as a lightweight alternative to Redux. ## Features ✨ **Lightweight** - Minimal footprint, maximum power šŸ”„ **Pub-Sub Pattern** - Simple event-based state management šŸ’¾ **Persistence Support** - Optional state persistence across sessions šŸ“˜ **TypeScript Support** - Full type definitions included šŸŽÆ **No Dependencies** - Zero runtime dependencies (peer dependency optional for persistence) ## Installation ```bash npm install state-manager-utility --save ``` ## Quick Start ### JavaScript ```javascript import { EmitEvent, SubscribeToEvent, UnsubscribeEvent } from 'state-manager-utility'; // Emit an event with data EmitEvent({ event: 'user:login', data: { userId: 123, name: 'John' } }); // āš ļø IMPORTANT: Always define callback as a named function (not inline) // This ensures you can properly unsubscribe using the same reference function handleUserLogin(data) { console.log('User logged in:', data); } // Subscribe to events SubscribeToEvent({ event: 'user:login', callback: handleUserLogin // Named function reference }); // Cleanup: Unsubscribe when no longer needed UnsubscribeEvent({ event: 'user:login', callback: handleUserLogin // Same reference used for unsubscribing }); ``` ### TypeScript ```typescript import { EmitEvent, SubscribeToEvent, UnsubscribeEvent } from 'state-manager-utility'; interface User { userId: number; name: string; } // Emit typed events EmitEvent({ event: 'user:login', data: { userId: 123, name: 'John' } as User }); // Define callback as a named function for proper unsubscription function handleUserLogin(data: User, meta?: { event: string, extraParams: any }) { console.log('User logged in:', data.name); console.log('Event name:', meta?.event); } // Subscribe with typed callback SubscribeToEvent({ event: 'user:login', callback: handleUserLogin // Named function reference }); // Cleanup: Unsubscribe when no longer needed UnsubscribeEvent({ event: 'user:login', callback: handleUserLogin // Same reference }); ``` ## API Reference ### EmitEvent Broadcasts an event with data. The event can be stored for future subscribers or transmitted temporarily. ```typescript EmitEvent({ event: string, data: any, isMemoryStore?: boolean, isTemp?: boolean, dontTransmit?: boolean }): void ``` **Parameters:** - `event` (string) - Unique identifier for the event - `data` (any) - Data to be broadcasted - `isMemoryStore` (boolean, optional) - If `true`, persists event across sessions (requires storage utility initialization) - `isTemp` (boolean, optional) - If `true`, event is broadcasted only once without storing - `dontTransmit` (boolean, optional) - If `true`, stores the event but doesn't broadcast immediately **Example:** ```javascript EmitEvent({ event: 'cart:updated', data: { items: 3, total: 99.99 }, isMemoryStore: true // Persist across sessions }); ``` --- ### SubscribeToEvent Subscribes to an event and receives callbacks when the event is emitted. ```typescript SubscribeToEvent({ event: string, callback: (payload?: any, meta?: { event: string, extraParams: any }) => void, extraParams?: any, isMemoryStore?: boolean, isTemp?: boolean, skipOldEvent?: boolean }): void ``` **Parameters:** - `event` (string) - Event identifier to subscribe to - `callback` (function) - Function called when event is emitted. Receives `(data, { event, extraParams })` - `extraParams` (any, optional) - Additional parameters passed back in callback - `isMemoryStore` (boolean, optional) - Subscribe to persisted events - `isTemp` (boolean, optional) - Unsubscribe automatically after first callback - `skipOldEvent` (boolean, optional) - If `true`, doesn't trigger callback for existing event data **Example:** ```javascript // Define callback as named function function handleCartUpdate(data, meta) { console.log(`Cart has ${data.items} items`); console.log('Extra params:', meta.extraParams); } SubscribeToEvent({ event: 'cart:updated', callback: handleCartUpdate, extraParams: { source: 'header-component' } }); // Cleanup when done UnsubscribeEvent({ event: 'cart:updated', callback: handleCartUpdate }); ``` --- ### UnsubscribeEvent Removes an event listener. ```typescript UnsubscribeEvent({ event: string, callback: (payload?: any) => void, isMemoryStore?: boolean }): void ``` **Parameters:** - `event` (string) - Event identifier - `callback` (function) - Same callback function reference used in `SubscribeToEvent` - `isMemoryStore` (boolean, optional) - Target persisted event store **Example:** ```javascript // āš ļø CRITICAL: Use the same function reference for both subscribe and unsubscribe function handleCartUpdate(data) { console.log('Cart updated:', data); } // Subscribe SubscribeToEvent({ event: 'cart:updated', callback: handleCartUpdate // Store this reference }); // Later, unsubscribe using the SAME reference UnsubscribeEvent({ event: 'cart:updated', callback: handleCartUpdate // Must be the same function }); // āŒ WRONG: This will NOT work (inline functions create new references) // SubscribeToEvent({ // event: 'cart:updated', // callback: (data) => console.log(data) // New reference // }); // UnsubscribeEvent({ // event: 'cart:updated', // callback: (data) => console.log(data) // Different reference - won't unsubscribe! // }); ``` --- ### GetBroadcasterData Retrieves stored event data without subscribing. ```typescript GetBroadcasterData({ event: string, isMemoryStore?: boolean }): { success: true, response: any } | { success: false } ``` **Parameters:** - `event` (string) - Event identifier - `isMemoryStore` (boolean, optional) - Retrieve from persisted store **Returns:** - Object with `success: true` and `response` (event data) if found - Object with `success: false` if event not found **Example:** ```javascript const result = GetBroadcasterData({ event: 'user:login' }); if (result.success) { console.log('User data:', result.response); } ``` --- ### DeleteEvent Removes an event from the store. All listeners will stop receiving updates. ```typescript DeleteEvent({ event: string, isMemoryStore?: boolean }): void ``` **Parameters:** - `event` (string) - Event identifier to remove - `isMemoryStore` (boolean, optional) - Delete from persisted store **Example:** ```javascript DeleteEvent({ event: 'temp:session', isMemoryStore: false }); ``` --- ### HasEventSubscribed Checks if anyone is currently subscribed to an event. ```typescript HasEventSubscribed({ event: string, isMemoryStore?: boolean }): boolean ``` **Parameters:** - `event` (string) - Event identifier - `isMemoryStore` (boolean, optional) - Check persisted store **Returns:** - `true` if there are active subscribers - `false` if no subscribers **Example:** ```javascript if (HasEventSubscribed({ event: 'notifications:new' })) { console.log('Someone is listening to notifications'); } ``` --- ### InitialiseStateManager Initialize the library with storage utilities for persistence support. Only required if you want events to persist across sessions. ```typescript InitialiseStateManager({ StorageUtils: { SetItem: (key: string, value: any) => void, GetItem: (key: string) => any } }): void ``` **Parameters:** - `StorageUtils` (object) - Object with `SetItem` and `GetItem` functions from [storage-utility](https://www.npmjs.com/package/storage-utility) **Example:** ```javascript import { InitialiseStateManager } from 'state-manager-utility'; import { SetItem, GetItem } from 'storage-utility'; // Initialize with storage utilities InitialiseStateManager({ StorageUtils: { SetItem, GetItem } }); // Now you can use isMemoryStore option EmitEvent({ event: 'user:preferences', data: { theme: 'dark' }, isMemoryStore: true // Will persist across sessions }); ``` ## Best Practices ### āš ļø Always Use Named Functions for Callbacks **CRITICAL:** When subscribing to events, always define callbacks as named functions (not inline arrow functions) if you plan to unsubscribe later. This is because `UnsubscribeEvent` requires the exact same function reference to properly remove the listener. ```javascript // āœ… CORRECT: Named function allows proper unsubscription function handleEvent(data) { console.log(data); } SubscribeToEvent({ event: 'myEvent', callback: handleEvent }); UnsubscribeEvent({ event: 'myEvent', callback: handleEvent }); // Works! // āŒ WRONG: Inline functions create different references SubscribeToEvent({ event: 'myEvent', callback: (data) => console.log(data) // Reference #1 }); UnsubscribeEvent({ event: 'myEvent', callback: (data) => console.log(data) // Reference #2 - Different! Won't work! }); ``` **Why this matters:** - JavaScript compares function references, not function content - Each inline function creates a new reference, even if the code is identical - Without matching references, `UnsubscribeEvent` cannot find and remove the listener - This leads to memory leaks and unexpected behavior **Exception:** You can use inline functions for temporary subscriptions (`isTemp: true`) since they auto-cleanup. ## Usage Patterns ### React Component Example ```javascript import React, { useEffect, useState } from 'react'; import { EmitEvent, SubscribeToEvent, UnsubscribeEvent } from 'state-manager-utility'; function CartComponent() { const [cartCount, setCartCount] = useState(0); useEffect(() => { const handleCartUpdate = (data) => { setCartCount(data.items); }; // Subscribe to cart updates SubscribeToEvent({ event: 'cart:updated', callback: handleCartUpdate }); // Cleanup on unmount return () => { UnsubscribeEvent({ event: 'cart:updated', callback: handleCartUpdate }); }; }, []); const addToCart = () => { EmitEvent({ event: 'cart:updated', data: { items: cartCount + 1 } }); }; return ( <div> <p>Cart Items: {cartCount}</p> <button onClick={addToCart}>Add to Cart</button> </div> ); } ``` ### Temporary Events (One-time Subscription) ```javascript // Note: For temp subscriptions, inline functions are acceptable since no manual cleanup needed // The subscription auto-removes after first callback SubscribeToEvent({ event: 'modal:opened', callback: (data) => { console.log('Modal opened once:', data); }, isTemp: true // Auto-unsubscribes after first callback }); // Or with named function if you need to cancel before it fires function handleModalOpen(data) { console.log('Modal opened:', data); } SubscribeToEvent({ event: 'modal:opened', callback: handleModalOpen, isTemp: true }); // You can still manually unsubscribe before it fires if needed UnsubscribeEvent({ event: 'modal:opened', callback: handleModalOpen }); ``` ### Skip Old Events ```javascript // Define callback as named function function handleLiveUpdate(data) { console.log('New update:', data); } // Don't trigger callback with existing data, only new emissions SubscribeToEvent({ event: 'live:updates', callback: handleLiveUpdate, skipOldEvent: true // Only react to new events }); // Cleanup when component unmounts UnsubscribeEvent({ event: 'live:updates', callback: handleLiveUpdate }); ``` ## When to Use āœ… **Good for:** - Small to medium React/Vue/Angular applications - Component communication without prop drilling - Simple state sharing across unrelated components - Event-driven architectures - Lightweight state management needs āš ļø **Consider alternatives for:** - Large applications with complex state logic (use Redux/MobX) - Applications requiring time-travel debugging - Heavy state transformations and middleware requirements ## License MIT Ā© [Shubham Kesarwani](https://github.com/shubhamkes) ## Repository [GitHub Repository](https://github.com/drivezy/state-manager.git) ## Author **Shubham Kesarwani** šŸ“§ shubham.kesarwani89@gmail.com šŸ”— [GitHub](https://github.com/shubhamkes)