A tiny, framework-agnostic state store for the web. Simple, reactive, and immutable. Works with everything.
structuredClone
if available, falls back to JSON.parse(JSON.stringify(...))
to ensure state updates are truly immutable.get
, set
, and subscribe
.Method | Description |
---|---|
get() |
Returns the current state. |
set((state) => {}) or set(value) |
Replaces the state. Accepts either a new state or an updater function that returns new state. |
subscribe((state => {})), false) |
Subscribes a listener function to state changes. Returns an unsubscribe function. Setting false to true will run the listener instantly. |
InitError
if initialState
is undefined, defaulting to null.TypeError
if an invalid object (e.g., string, number, null) is passed to _freeze
, expecting a plain object or array.CloneError
if an object or array cannot be cloned due to non-serializable data (e.g., functions, Date objects, circular references).SetError
if set is called with undefined.NoOp
warning if an updater function returns undefined.SubscribeError
if a non-function is passed to subscribe, ignoring the subscription.npm install eis
# or
yarn add eis
Via CDN:
import eis from 'https://unpkg.com/@taujor/eis/eis.js'
Or include directly in your project:
import eis from './eis.js'
import eis from 'eis';
const [get, set, subscribe] = eis({ count: 0 });
// Subscribe to state changes
const unsubscribe = subscribe((state) => {
console.log('State changed:', state);
}, true);
// Update state
set({ count: 1 });
// Logs: State changed: { count: 1 }
set((state) => ({ count: state.count + 1 }));
// Logs: State changed: { count: 2 }
// Get current state
console.log(get());
// { count: 2 }
// Unsubscribe
unsubscribe();
import { useEffect, useState } from 'react';
import eis from './eis.js';
// Initialize the state store with a number
const [get, set, subscribe] = eis(0);
function Counter() {
// Use React state to trigger re-renders
const [count, setCount] = useState(get());
// Subscribe to eis state changes
useEffect(() => {
const unsubscribe = subscribe((newState) => {
console.log('Count:', newState);
setCount(newState); // Update React state
}, true); // Immediate invocation to sync initial state
return unsubscribe; // Cleanup on unmount
}, []);
return (
<div style={{ padding: '20px' }}>
<h1>Simple Counter</h1>
<p>Count: {count}</p>
<button
onClick={() => set((state) => state + 1)}
style={{ marginRight: '10px' }}
>
Increment
</button>
<button
onClick={() => set((state) => state - 1)}
>
Decrement
</button>
</div>
);
}
export default Counter;
CloneError
or FreezeError
.CloneError
during cloning.JSON.parse(JSON.stringify(...))
does not handle Symbol
or undefined
values so for consistency non-serializable objects like this are also rejected when using structuredClone
.update
will be added to the API which instead of replacing state like set does will merge the given state into the current state._isSerializable
helper for better error handling.structuredClone
if available, falls back to JSON methods._freeze
now properly handles arrays and already frozen objects._freeze
does not attempt to freeze top-level or nested primitive data types in objects or arrays.set
now passes a cloned state to updater functions (no need to spread state into a new object before returning from updater functions)._freeze
and _clone
.Contributions are welcome! Please open an issue or submit a pull request.