@minute-spa/appstate
v0.0.2
Published
A simple vanilla app state service.
Readme
AppState
⚠️ DEPRECATED: This package is deprecated. Please use the public MinSPA repository for the newest code: https://github.com/devosm1030/minspa
A lightweight, flexible state management library for JavaScript applications with built-in persistence support using browser sessionStorage.
This module is a component of the Minute SPA framework (https://github.com/devosm1030/minute-spa), but is an standalone component that can be used independently of the framework.
Zero dependencies - use in your project via NPM, or simply clone the code right into your project for a truly vanilla experience that requires no packagers or bundlers!
Table of Contents
Overview
AppState provides a simple yet powerful way to manage application state with a publish-subscribe pattern. It supports multiple isolated state instances, automatic persistence to sessionStorage, and proxy-based direct property access.
Key Features
- Event-based state management - Subscribe to state changes with callback functions
- Automatic persistence - Optional sessionStorage integration when used in browser environment for tab-lifetime persistence
- Multiple state instances - Create isolated state contexts with unique identifiers
- Proxy support - Access state directly as object properties or through methods
- Browser and Node.js compatible - Gracefully handles environments without
windowobject
Installation and Usage
For NodeJS projects
Installation
In your project directory, install the dependency on the command line:
npm install --save @minute-spa/appstateUsage
Import the package in your code:
import { appState } from '@minute-spa/appstate'For Vanila projects
Vanilla Installation
Clone https://github.com/devosm1030/minute-spa/blob/main/packages/AppState/index.js into your project and rename as appropriate.
Vanilla Usage
From an ES module, import the package in your code:
import { appState } from '<path/to/your/file>'API Reference
Default state service: appState
The default appState instance that can be used globally in your application.
import { appState } from '@minute-spa/appstate';appStateFor(stateId)
Creates or retrieves an isolated AppState instance with a unique identifier. When you want to create multiple app state services within your application.
Parameters:
stateId(string, required) - Unique identifier for the state instance
Returns: AppState instance (proxied)
Throws: Error if stateId is not provided
Example:
import { appStateFor } from '@minute-spa/appstate';
const authState = appStateFor('auth');
const uiState = appStateFor('ui');
// These states are completely isolated
authState.set('token', 'abc123');
uiState.set('sidebarOpen', true);
console.log(authState.get('token')); // 'abc123'
console.log(uiState.get('token')); // undefinedappState.on(eventName, callback)
Subscribe to state changes for a specific event.
Parameters:
eventName(string, required) - Name of the event to subscribe tocallback(function, required) - Function called when state changes. Receives the new state value as argument.
Returns: undefined
Throws: Error if eventName is not a string
Behavior:
- If state already exists for the event, callback is immediately called with current value
- Multiple callbacks can subscribe to the same event
- Callbacks are executed in the order they were registered
- Callbacks are executed on EVERY request to update the event, regardless of whether or not the event or it's contents have changed.
Example:
// Subscribe to user changes
appState.on('user', (userData) => {
console.log('User changed:', userData);
});
// Subscribe multiple callbacks to same event
appState.on('user', (userData) => {
updateUI(userData);
});
appState.on('user', (userData) => {
logAnalytics('user_changed', userData);
});
// Set user - all callbacks will be triggered
appState.set('user', { id: 1, name: 'Alice' });Immediate callback execution:
// Set state first
appState.set('config', { theme: 'dark' });
// Subscribe later - callback is immediately called with current value
appState.on('config', (config) => {
console.log('Current config:', config); // Logs immediately
});appState.off(eventName, callback?)
Unsubscribe from state changes.
Parameters:
eventName(string, required) - Name of the event to unsubscribe fromcallback(function, optional) - Specific callback to remove. If omitted, all callbacks for the event are removed.
Returns: undefined
Throws: Error if eventName is not a string
Example:
const userHandler = (user) => {
console.log('User:', user);
};
// Subscribe
appState.on('user', userHandler);
// Unsubscribe specific callback
appState.off('user', userHandler);
// Or remove all subscribers for an event
appState.on('user', handler1);
appState.on('user', handler2);
appState.off('user'); // Removes both handler1 and handler2appState.set(eventName, data, persist?)
Set or update state and notify subscribers. Subscribers will be notified regardless of whether or not the data for the event has changed from it's prior value.
Parameters:
eventName(string, required) - Name of the event/state keydata(any, required) - Value to store (must be JSON-serializable if persisting)persist(boolean, optional, default:false) - Whether to persist to sessionStorage. Only applicable in a browser envrionment.
Returns: The data value that was set, or undefined if parameters are invalid
Throws: Error if eventName is not a string
Example:
// Basic usage
appState.set('count', 42);
// With persistence (survives page refresh within same tab)
appState.set('userPreferences', { theme: 'dark', language: 'en' }, true);
// Complex data structures
appState.set('todos', [
{ id: 1, text: 'Learn AppState', done: true },
{ id: 2, text: 'Build app', done: false }
]);
// Chainable pattern (returns the data)
const savedUser = appState.set('user', { name: 'Bob' });
console.log(savedUser); // { name: 'Bob' }Persistence behavior:
// First time setting with persist
appState.set('token', 'secret123', true);
// Refresh page - state is restored from sessionStorage
// Update persisted state (automatically stays persisted)
appState.set('token', 'newSecret456'); // Still persisted!
// To stop persisting, you must delete and re-set without persist flag
appState.delete('token');
appState.set('token', 'nonPersisted');appState.get(eventName)
Retrieve current state value.
Parameters:
eventName(string, required) - Name of the event/state key
Returns: The stored value, or undefined if not found
Throws: Error if eventName is not a string
Example:
appState.set('config', { apiUrl: 'https://api.example.com' });
const config = appState.get('config');
console.log(config.apiUrl); // 'https://api.example.com'
// Non-existent keys return undefined
const missing = appState.get('nonExistent'); // undefinedappState.delete(eventName, broadcast?)
Remove state and optionally notify subscribers.
Parameters:
eventName(string, required) - Name of the event/state key to deletebroadcast(boolean, optional, default:false) - Whether to notify subscribers withundefined
Returns: undefined
Example:
appState.set('temp', 'temporary data');
console.log(appState.get('temp')); // 'temporary data'
// Delete without notifying subscribers
appState.delete('temp');
console.log(appState.get('temp')); // undefined
// Delete with notification
appState.on('user', (user) => {
console.log('User is now:', user);
});
appState.set('user', { name: 'Charlie' }); // Logs: "User is now: { name: 'Charlie' }"
appState.delete('user', true); // Logs: "User is now: undefined"appState.reset()
Clear all state and subscribers, returning to initial state.
Parameters: None
Returns: undefined
Example:
appState.set('key1', 'value1');
appState.set('key2', 'value2', true); // persisted
appState.on('key1', () => {});
appState.reset();
// All state cleared
console.log(appState.get('key1')); // undefined
console.log(appState.get('key2')); // undefined (even persisted data is removed)
// All subscribers removed
// Subscribers will not be calledProxy Property Access
AppState instances support direct property access through JavaScript Proxy.
Example:
// Setting values
appState.userName = 'Alice';
appState.isLoggedIn = true;
appState.settings = { theme: 'dark' };
appState.on('userName', userName => {
console.log(userName); // 'Alice'
})
// Getting values
console.log(appState.userName); // 'Alice'
console.log(appState.isLoggedIn); // true
// Equivalent to:
appState.set('userName', 'Alice');
appState.get('userName');
// Note: Proxy access does NOT support persistence flag
// Use explicit set() method for persistence
appState.set('userName', 'Alice', true); // PersistedAdvanced Usage
Multiple State Instances
Create isolated state contexts for different parts of your application:
import { appStateFor } from '@minute-spa/appstate';
const authState = appStateFor('auth');
const cartState = appStateFor('cart');
const notificationState = appStateFor('notifications');
// Each instance is completely independent
authState.set('token', 'jwt-token-here', true);
cartState.set('items', [], true);
notificationState.set('unread', 0);Persistence Patterns
// User preferences that survive page refresh
appState.set('preferences', {
theme: 'dark',
language: 'en',
notifications: true
}, true);
// Session-only data (not persisted)
appState.set('tempFormData', { field1: 'value' });Cleanup and Memory Management
const handler = (data) => console.log(data);
// Subscribe
appState.on('event', handler);
// Clean up when component unmounts
function cleanup() {
appState.off('event', handler);
}
// Or reset everything (useful for testing)
afterEach(() => {
appState.reset();
});Browser Compatibility
AppState works in all modern browsers and Node.js environments:
- Browser: Requires sessionStorage support (IE 8+, all modern browsers)
- Node.js: Works without browser-specific features (persistence disabled)
License
MIT License - See notice in index.js comments.
