This catalog documents all built-in hooks provided by Beacon. Each hook is available as a separate import from @nerdalytics/beacon/hooks.
- Debugging Hooks
- Performance Hooks
- Persistence Hooks
- Validation Hooks
- Analytics Hooks
- DevTools Hooks
- Utility Hooks
Logs property read operations to the console.
Import:
import { logRead } from '@nerdalytics/beacon/hooks/logRead';Signature:
function logRead<T>(
options?: {
prefix?: string;
logger?: (...args: any[]) => void;
includeStack?: boolean;
}
): StateHooks<T>['onRead']Options:
prefix- Custom prefix for log messages (default:'[READ]')logger- Custom logging function (default:console.log)includeStack- Include stack trace (default:false)
Example:
const $state = state({ count: 0 }, {
onRead: logRead({
prefix: '[DEBUG]',
includeStack: true
})
});
const value = $state.count;
// Logs: "[DEBUG] count = 0"
// Plus stack trace if includeStack is trueLogs property write operations to the console.
Import:
import { logWrite } from '@nerdalytics/beacon/hooks/logWrite';Signature:
function logWrite<T>(
options?: {
prefix?: string;
logger?: (...args: any[]) => void;
includeStack?: boolean;
logUnchanged?: boolean;
}
): StateHooks<T>['onWrite']Options:
prefix- Custom prefix for log messages (default:'[WRITE]')logger- Custom logging function (default:console.log)includeStack- Include stack trace (default:false)logUnchanged- Log even when value doesn't change (default:false)
Example:
const $state = state({ name: 'Alice' }, {
onWrite: logWrite({
prefix: '[MUTATION]',
logUnchanged: true
})
});
$state.name = 'Bob';
// Logs: "[MUTATION] name: Alice → Bob"
$state.name = 'Bob'; // Same value
// Logs: "[MUTATION] name: Bob → Bob (unchanged)"Logs effect lifecycle events.
Import:
import { logEffect } from '@nerdalytics/beacon/hooks/logEffect';Signature:
function logEffect(
options?: {
logRun?: boolean;
logDispose?: boolean;
logDependencies?: boolean;
logTiming?: boolean;
}
): EffectHooks['onRun'] & EffectHooks['onDispose']Options:
logRun- Log when effect runs (default:true)logDispose- Log when effect is disposed (default:true)logDependencies- Log tracked dependencies (default:false)logTiming- Log execution time (default:false)
Example:
const dispose = effect(() => {
// Effect body
}, 'myEffect', {
onRun: logEffect({
logTiming: true,
logDependencies: true
})
});
// Logs: "[EFFECT:myEffect] Running..."
// Logs: "[EFFECT:myEffect] Dependencies: ..."
// Logs: "[EFFECT:myEffect] Completed in 0.5ms"Logs derive computation and caching.
Import:
import { logDerive } from '@nerdalytics/beacon/hooks/logDerive';Signature:
function logDerive<T>(
options?: {
logCompute?: boolean;
logCache?: boolean;
logValue?: boolean;
maxValueLength?: number;
}
): DeriveHooks<T>Options:
logCompute- Log computations (default:true)logCache- Log cache hits (default:true)logValue- Include computed value in logs (default:false)maxValueLength- Truncate logged values (default:100)
Example:
const $sum = derive(() => a + b, {
onCompute: logDerive({
logValue: true,
maxValueLength: 50
}).onCompute
});
// Logs: "[DERIVE] Computing... Result: 15"
// On cache hit: "[DERIVE] Cache hit #3: 15"Captures and logs stack traces for operations.
Import:
import { trace } from '@nerdalytics/beacon/hooks/trace';Signature:
function trace<T>(
options?: {
maxDepth?: number;
filter?: (line: string) => boolean;
onTrace?: (stack: string) => void;
}
): StateHooks<T>['onRead'] | StateHooks<T>['onWrite']Options:
maxDepth- Maximum stack frames to capture (default:10)filter- Filter stack frames (default: exclude node_modules)onTrace- Custom trace handler (default: console.trace)
Example:
const $state = state({ value: 0 }, {
onWrite: trace({
maxDepth: 5,
filter: (line) => !line.includes('node_modules')
})
});
$state.value = 1;
// Logs stack trace showing where the write originatedMeasures and reports performance metrics.
Import:
import { profile } from '@nerdalytics/beacon/hooks/profile';Signature:
function profile<T>(
options?: {
name?: string;
warnThreshold?: number;
onSlow?: (duration: number, operation: string) => void;
}
): StateHooks<T> & EffectHooks & DeriveHooksOptions:
name- Profile name for identification (default: auto-generated)warnThreshold- Milliseconds before warning (default:16- one frame)onSlow- Callback for slow operations
Example:
const $state = state({ data: [] }, {
onWrite: profile({
name: 'DataState',
warnThreshold: 10,
onSlow: (ms, op) => {
console.warn(`Slow ${op}: ${ms}ms`);
// Could send to monitoring service
}
}).onWrite
});Limits the frequency of operations.
Import:
import { throttle } from '@nerdalytics/beacon/hooks/throttle';Signature:
function throttle<T>(
ms: number,
options?: {
leading?: boolean;
trailing?: boolean;
}
): StateHooks<T>['onWrite']Options:
ms- Throttle interval in millisecondsleading- Allow immediate execution (default:true)trailing- Execute after throttle period (default:true)
Example:
const $search = state({ query: '' }, {
onWrite: throttle(500, {
leading: false,
trailing: true
})
});
// Rapid updates are throttled
$search.query = 'a'; // Queued
$search.query = 'ab'; // Replaces queued
$search.query = 'abc'; // Replaces queued
// After 500ms: Executes with 'abc'Delays operations until after a period of inactivity.
Import:
import { debounce } from '@nerdalytics/beacon/hooks/debounce';Signature:
function debounce<T>(
ms: number,
options?: {
maxWait?: number;
}
): StateHooks<T>['onWrite']Options:
ms- Debounce delay in millisecondsmaxWait- Maximum time to wait before forcing execution
Example:
const $input = state({ value: '' }, {
onWrite: debounce(300, {
maxWait: 1000
})
});
// Rapid typing is debounced
$input.value = 'h'; // Timer starts
$input.value = 'he'; // Timer resets
$input.value = 'hel'; // Timer resets
$input.value = 'hell'; // Timer resets
$input.value = 'hello'; // Timer resets
// After 300ms of inactivity: Executes with 'hello'
// Or after 1000ms total: Forces executionPersists state to storage (localStorage, sessionStorage, etc).
Import:
import { persist } from '@nerdalytics/beacon/hooks/persist';Signature:
function persist<T>(
key: string,
options?: {
storage?: Storage;
serialize?: (value: T) => string;
deserialize?: (data: string) => T;
debounce?: number;
}
): StateHooks<T>['onWrite']Options:
key- Storage keystorage- Storage backend (default:localStorage)serialize- Custom serializer (default:JSON.stringify)deserialize- Custom deserializer (default:JSON.parse)debounce- Debounce writes in ms (default:0)
Example:
const $settings = state({
theme: 'dark',
language: 'en'
}, {
onWrite: persist('app-settings', {
storage: sessionStorage,
debounce: 500
})
});
$settings.theme = 'light'; // Saved to sessionStorage after 500msLoads initial state from storage.
Import:
import { hydrate } from '@nerdalytics/beacon/hooks/hydrate';Signature:
function hydrate<T>(
key: string,
options?: {
storage?: Storage;
deserialize?: (data: string) => Partial<T>;
merge?: (stored: Partial<T>, initial: T) => T;
}
): (initial: T) => TOptions:
key- Storage keystorage- Storage backend (default:localStorage)deserialize- Custom deserializer (default:JSON.parse)merge- Merge strategy (default:Object.assign)
Example:
const initial = hydrate('app-settings')({
theme: 'dark',
language: 'en'
});
const $settings = state(initial, {
onWrite: persist('app-settings')
});
// State is hydrated from storage if availableValidates values before they are set.
Import:
import { validate } from '@nerdalytics/beacon/hooks/validate';Signature:
function validate<T>(
rules: {
[K in keyof T]?: (value: T[K]) => boolean | string;
} | ((prop: PropertyKey, value: unknown) => boolean | string),
options?: {
onError?: (error: ValidationError) => void;
preventDefault?: boolean;
}
): StateHooks<T>['onWrite']Options:
rules- Validation rules per property or global validatoronError- Error handler (default: console.error)preventDefault- Prevent invalid writes (default:false)
Example:
const $user = state({
email: '',
age: 0
}, {
onWrite: validate({
email: (value) => {
if (!value.includes('@')) {
return 'Invalid email format';
}
return true;
},
age: (value) => value >= 0 && value <= 120
}, {
preventDefault: true,
onError: (error) => {
console.error(`Validation failed: ${error.message}`);
}
})
});
$user.email = 'invalid'; // Prevented, logs error
$user.email = 'user@example.com'; // AllowedPrevents modifications to certain properties.
Import:
import { freeze } from '@nerdalytics/beacon/hooks/freeze';Signature:
function freeze<T>(
properties: Array<keyof T> | ((prop: PropertyKey) => boolean),
options?: {
throwError?: boolean;
onAttempt?: (prop: PropertyKey) => void;
}
): StateHooks<T>['onWrite'] & StateHooks<T>['onDelete']Options:
properties- Properties to freeze or predicate functionthrowError- Throw on modification attempt (default:false)onAttempt- Callback for modification attempts
Example:
const $config = state({
id: '123',
name: 'App',
version: '1.0.0'
}, {
onWrite: freeze(['id', 'version'], {
throwError: true,
onAttempt: (prop) => {
console.warn(`Attempted to modify frozen property: ${String(prop)}`);
}
})
});
$config.name = 'MyApp'; // Allowed
$config.id = '456'; // Throws errorTracks state mutations for analytics.
Import:
import { trackMutation } from '@nerdalytics/beacon/hooks/trackMutation';Signature:
function trackMutation<T>(
options?: {
eventName?: string;
tracker?: (event: MutationEvent) => void;
includeValue?: boolean;
includePrevious?: boolean;
}
): StateHooks<T>['onWrite']Options:
eventName- Event name for tracking (default:'state_mutation')tracker- Custom tracking function (default: console.log)includeValue- Include new value in event (default:true)includePrevious- Include old value in event (default:false)
Example:
const $user = state({
name: 'Alice',
lastLogin: null
}, {
onWrite: trackMutation({
eventName: 'user_update',
tracker: (event) => {
analytics.track(event.name, {
property: event.property,
value: event.value,
timestamp: event.timestamp
});
},
includeValue: true,
includePrevious: true
})
});Tracks state access patterns for analytics.
Import:
import { trackAccess } from '@nerdalytics/beacon/hooks/trackAccess';Signature:
function trackAccess<T>(
options?: {
eventName?: string;
tracker?: (event: AccessEvent) => void;
sampleRate?: number;
}
): StateHooks<T>['onRead']Options:
eventName- Event name for tracking (default:'state_access')tracker- Custom tracking functionsampleRate- Sampling rate 0-1 (default:1.0)
Example:
const $data = state({
sensitive: 'data',
public: 'info'
}, {
onRead: trackAccess({
eventName: 'data_access',
sampleRate: 0.1, // Track 10% of accesses
tracker: (event) => {
if (event.property === 'sensitive') {
audit.log('Sensitive data accessed', event);
}
}
})
});Integrates with browser DevTools for debugging.
Import:
import { devtools } from '@nerdalytics/beacon/hooks/devtools';Signature:
function devtools<T>(
options?: {
name?: string;
enable?: boolean;
logStateChanges?: boolean;
logEffects?: boolean;
logDerivations?: boolean;
trace?: boolean;
}
): StateHooks<T> & EffectHooks & DeriveHooksOptions:
name- Instance name in DevTools (default: auto-generated)enable- Enable DevTools integration (default:truein dev)logStateChanges- Log state changes (default:true)logEffects- Log effect executions (default:true)logDerivations- Log derive computations (default:true)trace- Include stack traces (default:false)
Example:
const $app = state({
user: null,
settings: {}
}, {
onRead: devtools({ name: 'AppState' }).onRead,
onWrite: devtools({ name: 'AppState' }).onWrite
});
// Creates DevTools panel with:
// - State tree visualization
// - Change history timeline
// - Effect dependency graph
// - Performance metricsBeacon supports providing hooks as arrays for natural composition without a compose utility:
Single Hook:
const $state = state(initial, {
onWrite: logWrite() // Single hook directly
});Multiple Hooks (Array):
const $state = state(initial, {
onWrite: [
logWrite(), // Runs first
validate(rules), // Runs second
persist('state-key'), // Runs third
trackMutation() // Runs fourth
]
});
// Array hooks execute in orderMixed Usage:
const $complex = state(data, {
onRead: logRead(), // Single hook
onWrite: [ // Multiple hooks
validate(rules),
persist('key'),
notify()
]
});Note: The compose utility is for internal use only. Users should use arrays for combining hooks.
Executes a hook only once.
Import:
import { once } from '@nerdalytics/beacon/hooks/once';Signature:
function once<T extends (...args: any[]) => void>(
hook: T
): TExample:
const $state = state({ initialized: false }, {
onWrite: once((prop, oldValue, newValue) => {
if (prop === 'initialized' && newValue === true) {
console.log('State initialized!');
// This only runs once
}
})
});
$state.initialized = true; // Logs
$state.initialized = false;
$state.initialized = true; // Doesn't log againConditionally executes hooks based on a predicate.
Import:
import { filter } from '@nerdalytics/beacon/hooks/filter';Signature:
function filter<T extends (...args: any[]) => void>(
predicate: (...args: Parameters<T>) => boolean,
hook: T
): TExample:
const $state = state({
public: 'data',
_private: 'secret'
}, {
onRead: filter(
(prop) => !String(prop).startsWith('_'),
logRead() // Only logs non-private properties
)
});
const pub = $state.public; // Logged
const priv = $state._private; // Not loggedYou can create your own hooks following the same patterns:
import type { StateHooks } from '@nerdalytics/beacon';
export function myCustomHook<T>(
config: MyConfig
): StateHooks<T>['onWrite'] {
return (prop, oldValue, newValue, target) => {
// Your custom logic here
// Best practices:
// 1. Handle errors gracefully
// 2. Keep operations fast
// 3. Avoid side effects that could break reactivity
// 4. Document behavior clearly
};
}- Single Responsibility - Each hook should do one thing well
- Configurable - Provide options for customization
- Type-Safe - Use TypeScript generics and types
- Error Handling - Don't let errors break core functionality
- Performance - Keep hooks lightweight and fast
- Documentation - Include examples and options
- Testing - Write unit tests for your hooks
Custom hooks can be published as separate packages:
{
"name": "@myorg/beacon-hooks-custom",
"peerDependencies": {
"@nerdalytics/beacon": "^2000.0.0"
}
}Users can then install and use your custom hooks:
import { state } from '@nerdalytics/beacon';
import { myCustomHook } from '@myorg/beacon-hooks-custom';
const $state = state(initial, {
onWrite: myCustomHook({ /* config */ })
});