state-manager-utility
Version:
Global state management library
493 lines (385 loc) ⢠12.5 kB
Markdown
# State Manager Utility
[](https://www.npmjs.com/package/state-manager-utility)
[](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)