@fr0st/state
v1.0.1
Published
Small reactive state primitives for values, effects, and keyed stores.
Maintainers
Readme
FrostState
Small, focused reactive state primitives for values, effects, and keyed stores. FrostState has zero runtime dependencies, works in Node and bundlers, and also ships a browser-friendly UMD bundle that exposes globalThis.State.
Highlights
- Named exports for tree-shaking
- Browser UMD bundle in
dist/ - No runtime dependencies
- JSDoc-powered IntelliSense
Installation
Node / bundlers
npm i @fr0st/stateFrostState is ESM-only. Use import syntax in Node and bundlers.
Browser (UMD)
Load the bundle from your own copy or a CDN:
<script src="/path/to/dist/frost-state.min.js"></script>
<!-- or -->
<script src="https://cdn.jsdelivr.net/npm/@fr0st/state@latest/dist/frost-state.min.js"></script>
<script>
const { StateStore, useEffect, useState } = globalThis.State;
const count = useState(0);
useEffect(() => {
console.log('count =', count());
});
count(1);
</script>Quick Start
Reactive values
import { useEffect, useState } from '@fr0st/state';
const first = useState('Ada');
const last = useState('Lovelace');
useEffect(() => {
console.log(`${first()} ${last()}`);
});
last('Byron'); // logs "Ada Byron" on the next microtask
first.value = 'Augusta'; // logs "Augusta Byron"Keyed stores
import { StateStore, useEffect } from '@fr0st/state';
const store = StateStore.wrap({
count: 0,
});
useEffect(() => {
console.log('count =', store.count);
});
store.count = 1; // logs "count = 1"TypeScript note: FrostState is written in JavaScript and uses JSDoc types, which most editors surface as IntelliSense.
API
FrostState exports three named APIs from @fr0st/state: useState, useEffect, and StateStore.
useState(value)
Creates a callable state accessor for a single value.
const state = useState(value);The returned accessor supports:
state(): read the current valuestate(next): write the current valuestate.get(markEffects = true): read the current value, optionally without effect trackingstate.set(next): write the current valuestate.value: read or write the current valuestate.previous: read the previous value after the last successful change
import { useState } from '@fr0st/state';
const state = useState('hello');
state(); // 'hello'
state('world');
state.get(); // 'world'
state.set('again');
state.value = 'done';
state.previous; // 'again'useEffect(callback, options)
Runs an effect immediately, tracks the states read during that run, and schedules re-runs when any of those states change.
const effect = useEffect(callback, options);Options:
options.weak: use aWeakRef-backed runner
The returned runner supports:
effect(): schedule a re-run in a microtaskeffect.sync(): run immediately
import { useEffect, useState } from '@fr0st/state';
const a = useState(1);
const b = useState(2);
const effect = useEffect(() => {
console.log(a() + b());
});
a(3); // logs 5 on the next microtask
effect.sync(); // logs immediatelyStateStore
Creates a callable, proxy-backed keyed store for state accessors. Property reads
return existing keys, property assignment writes keys, and missing property reads
return undefined. Effects that read missing keys subscribe to later assignment
without exposing those keys through enumeration.
const store = new StateStore();
const state = store(key, defaultValue);Instance API
The returned store supports:
store.key: read an existing keystore.key = value: write a keystore.use(key, defaultValue): retrieve or create a state accessorstore(key, defaultValue): retrieve or create a state accessor through the callable formstore.set(object): set top-level keys from an objectstore.has(key): check whether a key existsstore.keys(): iterate stored keys
import { StateStore, useEffect } from '@fr0st/state';
const store = new StateStore();
const count = store('count', 0);
store.set({ label: 'Clicks' });
useEffect(() => {
console.log(store.label, count());
});
count(1); // logs "Clicks 1"
store.count = 2; // logs "Clicks 2"
store.has('count'); // true
Array.from(store.keys()); // ['count', 'label']Static helpers
StateStore.wrap(value, options): wrap a plain object in a storeStateStore.merge(store, value, options): merge plain-object data into a store
import { StateStore } from '@fr0st/state';
const nested = StateStore.wrap(
{
user: {
name: 'Ada',
},
},
{ deep: true },
);
nested.user.name = 'Grace';
const settings = new StateStore();
StateStore.merge(
settings,
{
ui: {
theme: 'dark',
},
},
{ deep: true },
);
StateStore.merge(
settings,
{
ui: {
compact: true,
},
},
{ deep: true },
);
settings.ui.theme = 'light';
nested.user.name; // 'Grace'
settings.ui.theme; // 'light'
settings.ui.compact; // trueBehavior Notes
useEffect()tracks only the states read during the latest successful run.useEffect()schedules normal re-runs in a microtask, and.sync()bypasses that scheduling.store.set(...)assigns top-level keys only. Nested plain objects remain plain values.- Use
StateStore.wrap(..., { deep: true })orStateStore.merge(..., { deep: true })for nested reactive stores. - Missing property reads such as
store.missingreturnundefined. Reads made during effect tracking still subscribe to later assignment without exposing the key. - API keys such as
use,set,has, andkeysare reserved and cannot be used as state keys. - Weak effects rely on
WeakRef. The test suite usesnode --expose-gcto cover weak-reference behavior.
Development
npm test
npm run js-lint
npm run buildLicense
FrostState is released under the MIT License.
