API Reference
This page contains the complete API reference for Reflex, a reactive state management library inspired by ClojureScript's re-frame. All functions are exported from the main @flexsurfer/reflex package.
Table of Contents
- Database
- Events
- Subscriptions
- Effects
- Co-effects
- Interceptors
- Tracing & Debugging
- Hot Reloading
- Utilities
- TypeScript Types
Database
Functions for managing Reflex's single source of truth - the application database that holds your entire application state.
initAppDb
function initAppDb<T = Record<string, any>>(value: Db<T>): voidInitializes the global application database with the provided state object.
Parameters:
value: The initial state object for your application
Example:
import { initAppDb } from '@flexsurfer/reflex';
initAppDb({
counter: 0,
user: null,
todos: []
});getAppDb
function getAppDb<T = Record<string, any>>(): Db<T>Returns the current application database state. Mainly used internally and for debugging.
Returns: The current application database state
Events
Pure functions that process state transitions in response to dispatched events, forming the core of Reflex's event-driven architecture.
regEvent
function regEvent<T = Record<string, any>>(
id: Id,
handler: EventHandler<T>
): void
function regEvent<T = Record<string, any>>(
id: Id,
handler: EventHandler<T>,
interceptors: Interceptor<T>[]
): void
function regEvent<T = Record<string, any>>(
id: Id,
handler: EventHandler<T>,
cofx: [Id, ...any[]][]
): void
function regEvent<T = Record<string, any>>(
id: Id,
handler: EventHandler<T>,
cofx: [Id, ...any[]][],
interceptors: Interceptor<T>[]
): voidRegisters an event handler that processes state transitions.
Parameters:
id: Unique identifier for the eventhandler: Function that receives co-effects and returns effectscofx(optional): Array of co-effect specifications to injectinterceptors(optional): Array of interceptors to apply
Handler Function Signature:
(coeffects: CoEffects<T>, ...params: any[]) => Effects | voidExamples:
import { regEvent, RANDOM, NOW } from '@flexsurfer/reflex';
// Simple state update
regEvent('increment', ({ draftDb }) => {
draftDb.counter += 1;
});
// Event with parameters
regEvent('set-name', ({ draftDb }, name) => {
draftDb.user.name = name;
});
// Event with side effects
regEvent('save-user', ({ draftDb }, user) => {
draftDb.saving = true;
return [
['http', {
method: 'POST',
url: '/api/users',
body: user,
onSuccess: ['save-user-success'],
onFailure: ['save-user-error']
}]
];
});
// Event with co-effects
regEvent('log-action',
({ draftDb, now, random }, action) => {
draftDb.actionLog.push({
action,
timestamp: now,
id: random.toString(36)
});
},
[[NOW], [RANDOM]]
);⚠️ Important: When passing data from
draftDbto effects, always use thecurrent()function to get the current (final) value. ThedraftDbobject is an Immer draft proxy that will be finalized after the event completes, so passingdraftDbdata directly to effects will result in the empty proxy object.typescriptimport { regEvent, current } from '@flexsurfer/reflex'; regEvent('answer-question', ({ draftDb }, questionIndex, answerIndex) => { draftDb.userAnswers[questionIndex] = answerIndex // Use current() to pass the final value to the effect return [['local-storage-set', { key: 'userAnswers', value: current(draftDb.userAnswers) }]] })
dispatch
function dispatch(event: EventVector): voidDispatches an event to the event queue for asynchronous processing.
Parameters:
event: An event vector where the first element is the event ID and subsequent elements are parameters
Example:
import { dispatch } from '@flexsurfer/reflex';
dispatch(['increment']);
dispatch(['set-name', 'John Doe']);
dispatch(['update-todo', 123, { completed: true }]);regEventErrorHandler
function regEventErrorHandler(handler: ErrorHandler): voidRegisters a global error handler for unhandled exceptions in the event processing chain. Only one handler can be registered. Registering a new handler clears the existing handler.
Parameters:
handler: Error handler function
Handler Function Signature:
(originalError: Error, reflexError: Error & { data: any }) => voidExample:
import { regEventErrorHandler } from '@flexsurfer/reflex';
regEventErrorHandler((originalError, reflexError) => {
console.error('Event processing error:', originalError);
console.error('Reflex error data:', reflexError.data);
// Send to error reporting service
});defaultErrorHandler
function defaultErrorHandler(
originalError: Error,
reflexError: Error & { data: any }
): voidThe default error handler that logs errors to console. Used automatically unless overridden.
Subscriptions
Declarative queries that derive and reactively compute data from the application state, automatically updating components when dependencies change.
regSub
function regSub<R>(id: Id, computeFn?: (...values: any[]) => R, depsFn?: (...params: any[]) => SubVector[]): voidRegisters a subscription that creates reactive queries against the application state.
Parameters:
id: Unique identifier for the subscriptioncomputeFn(optional): Function that computes the subscription valuedepsFn(optional): Function that returns dependency subscription vectors
Examples:
import { regSub } from '@flexsurfer/reflex';
// Root subscription (direct property access)
// id should match key in Database
regSub('user');
regSub('todos');
// Computed subscription with dependencies
regSub('user-name', (user) => user.name, () => [['user']]);
// Parameterized subscription
regSub('todo-by-id',
(todos, id) => todos.find(todo => todo.id === id),
() => [['todos']]
);
// Chained parameterized subscription
regSub('todo-text-by-id',
(todo, _id) => todo.text,
(id) => [['todo-by-id', id]]
);
// Computed subscription with multiple dependencies
regSub('user-display-name',
(name, prefix) => `${prefix}: ${name}`,
() => [['user-name'], ['display-prefix']]
);useSubscription
function useSubscription<T>(subVector: SubVector, componentName?: string): TReact hook that subscribes to a subscription and returns its current value.
Parameters:
subVector: Subscription vector where first element is subscription ID and rest are parameterscomponentName(optional): Name for debugging purposes
Returns: Current subscription value
Examples:
import { useSubscription } from '@flexsurfer/reflex';
function Counter() {
const counter = useSubscription<number>(['counter']);
return <div>Count: {counter}</div>;
}
function UserProfile() {
const name = useSubscription<string>(['user-name']);
const todo = useSubscription(['todo-by-id', 123]);
const todoText = useSubscription(['todo-text-by-id', 123]);
return <div>{name}</div>;
}getSubscriptionValue
function getSubscriptionValue<T>(subVector: SubVector): TGets the current value of a subscription without React hooks. Mainly used for testing and debugging.
Parameters:
subVector: Subscription vector
Returns: Current subscription value
Effects
Isolated handlers for side effects like HTTP requests, local storage, and navigation, keeping event handlers pure and testable.
regEffect
function regEffect(id: string, handler: EffectHandler): voidRegisters an effect handler that performs side effects.
Parameters:
id: Unique identifier for the effecthandler: Function that performs the side effect
Handler Function Signature:
(value: any) => voidExamples:
import { regEffect } from '@flexsurfer/reflex';
// HTTP effect
regEffect('http', async (config) => {
const response = await fetch(config.url, {
method: config.method,
body: JSON.stringify(config.body),
headers: { 'Content-Type': 'application/json' }
});
if (response.ok) {
const data = await response.json();
dispatch([config.onSuccess, data]);
} else {
dispatch([config.onFailure, response.status]);
}
});
// Local storage effect
regEffect('local-storage', (config) => {
localStorage.setItem(config.key, JSON.stringify(config.value));
});
// Navigation effect
regEffect('navigate', (path) => {
window.history.pushState(null, '', path);
});Built-in Effect Constants:
const DISPATCH = 'dispatch'
const DISPATCH_LATER = 'dispatch-later'Constants for built-in effect identifiers that handle event dispatching.
Examples:
import { regEvent, DISPATCH, DISPATCH_LATER } from '@flexsurfer/reflex';
// Immediate dispatch after processing
regEvent('process-and-notify', ({ draftDb }) => {
draftDb.processing = false;
draftDb.lastProcessed = Date.now();
return [
[DISPATCH, ['show-notification', 'Processing complete']]
];
});
// Delayed dispatch for auto-save
regEvent('start-auto-save', ({ draftDb }) => {
draftDb.autoSavePending = true;
return [
[DISPATCH_LATER, {
event: ['perform-auto-save'],
delay: 2000
}]
];
});
// Multiple dispatches
regEvent('complex-workflow', ({ draftDb }, data) => {
draftDb.workflow.status = 'processing';
return [
[DISPATCH, ['validate-data', data]],
[DISPATCH_LATER, {
event: ['process-data', data],
delay: 100
}],
[DISPATCH_LATER, {
event: ['cleanup-workflow'],
delay: 5000
}]
];
});Co-effects
Mechanisms for injecting external data like timestamps, random values, and results into event handlers.
regCoeffect
function regCoeffect(id: string, handler: CoEffectHandler): voidRegisters a co-effect handler that injects external values into event handlers.
Parameters:
id: Unique identifier for the co-effecthandler: Function that injects the co-effect
Handler Function Signature:
(coeffects: CoEffects<T>, value?: any) => CoEffects<T>Examples:
import { regCoeffect } from '@flexsurfer/reflex';
// Custom co-effects
regCoeffect('current-time', (coeffects) => ({
...coeffects,
currentTime: Date.now()
}));
regCoeffect('random-uuid', (coeffects) => ({
...coeffects,
uuid: crypto.randomUUID()
}));
regCoeffect('local-storage', (coeffects) => ({
...coeffects,
localStorage: window.localStorage
}));Built-in Co-effect Constants:
const NOW = 'now'
const RANDOM = 'random'Constants for built-in co-effect identifiers that inject timestamp and random values.
Examples:
import { regEvent, NOW, RANDOM } from '@flexsurfer/reflex';
// Using NOW coeffect to inject current timestamp
regEvent('log-user-action', ({ draftDb, now }) => {
draftDb.userActions.push({
action: 'button-click',
timestamp: now
});
}, [[NOW]]);
// Using RANDOM coeffect to generate unique IDs
regEvent('create-new-item', ({ draftDb, random }) => {
const newItem = {
id: `item-${random}`,
created: Date.now()
};
draftDb.items.push(newItem);
draftDb.selectedItemId = newItem.id;
}, [[RANDOM]]);
// Using both NOW and RANDOM together
regEvent('generate-session-id', ({ draftDb, now, random }) => {
draftDb.session = {
id: `session-${now}-${random}`,
startTime: now,
active: true
};
}, [[NOW], [RANDOM]]);Interceptors
Composable middleware that can transform event processing, enabling cross-cutting concerns like logging, validation, and error handling.
regGlobalInterceptor
function regGlobalInterceptor(interceptor: Interceptor): voidRegisters a global interceptor that applies to all events.
Parameters:
interceptor: Interceptor object withid,before, and/oraftermethods
Example:
import { regGlobalInterceptor } from '@flexsurfer/reflex';
const loggingInterceptor = {
id: 'logging',
before: (context) => {
console.log('Processing event:', context.coeffects.event);
return context;
},
after: (context) => {
console.log('Event completed, new DB:', context.coeffects.newDb);
return context;
}
};
regGlobalInterceptor(loggingInterceptor);getGlobalInterceptors
function getGlobalInterceptors(): Interceptor[]Returns all registered global interceptors.
clearGlobalInterceptors
function clearGlobalInterceptors(): void
function clearGlobalInterceptors(id: string): voidClears all global interceptors or a specific one by ID.
Parameters:
id(optional): Specific interceptor ID to clear
Tracing & Debugging
Performance monitoring and debugging utilities that provide visibility into event processing, subscription updates, and system behavior.
enableTracing
function enableTracing(): voidEnables performance tracing for events, subscriptions, and effects.
disableTracing
function disableTracing(): voidDisables performance tracing.
registerTraceCb
function registerTraceCb(key: string, cb: TraceCallback): voidRegisters a callback to receive trace data.
Parameters:
key: Unique key for the callbackcb: Callback function that receives trace data
Callback Signature:
(traces: Trace[]) => voidExample:
import { registerTraceCb, enableTracing } from '@flexsurfer/reflex';
enableTracing();
registerTraceCb('my-tracer', (traces) => {
traces.forEach(trace => {
console.log(`Operation: ${trace.operation}, Duration: ${trace.duration}ms`);
});
});enableTracePrint
function enableTracePrint(): voidEnables default console logging for trace data.
Hot Reloading
Development-time utilities that enable seamless code updates without losing application state, improving the development experience.
registerHotReloadCallback
function registerHotReloadCallback(callback: HotReloadCallback): () => voidRegisters a callback to be called when subscriptions are hot reloaded.
Parameters:
callback: Function to call on hot reload
Returns: Function to unregister the callback
triggerHotReload
function triggerHotReload(): voidManually triggers hot reload callbacks.
clearHotReloadCallbacks
function clearHotReloadCallbacks(): voidClears all hot reload callbacks.
useHotReload
function useHotReload(): voidReact hook that forces component re-render when subscriptions are hot reloaded.
Example:
import { useHotReload } from '@flexsurfer/reflex';
function MyComponent() {
useHotReload(); // Component will re-render on hot reload
return <div>My component content</div>;
}useHotReloadKey
function useHotReloadKey(): stringReact hook that provides a key that changes when subscriptions are hot reloaded, forcing complete re-mount.
Returns: A unique key that changes on hot reload
Example:
import { useHotReloadKey } from '@flexsurfer/reflex';
function MyComponent() {
const key = useHotReloadKey();
return (
<div key={key}> {/* Forces complete re-mount on hot reload */}
My component content
</div>
);
}setupSubsHotReload
function setupSubsHotReload(): {
dispose: () => void;
accept: (newModule?: any) => void;
}Utility for setting up hot reload in subscription modules.
Returns: Object with dispose and accept functions for HMR
Example (in subscriptions module):
import { setupSubsHotReload } from '@flexsurfer/reflex';
// Register subscriptions
regSub('counter');
// Setup hot reload
const { dispose, accept } = setupSubsHotReload();
// In your build tool's HMR setup
if (import.meta.hot) {
import.meta.hot.dispose(dispose);
import.meta.hot.accept(accept);
}HotReloadWrapper
function HotReloadWrapper(props: { children: React.ReactNode }): React.ReactNodeReact component that wraps children with hot reload support.
Props:
children: React children to wrap
Example:
import { HotReloadWrapper } from '@flexsurfer/reflex';
function App() {
return (
<HotReloadWrapper>
<MyAppContent />
</HotReloadWrapper>
);
}Utilities
Helper functions for common patterns like debouncing, throttling, and clearing handlers during testing and hot reloading.
debounceAndDispatch
function debounceAndDispatch(event: EventVector, durationMs: number): voidDispatches an event after a delay, canceling any previous dispatch of the same event.
Parameters:
event: Event vector to dispatchdurationMs: Delay in milliseconds
Example:
import { debounceAndDispatch } from '@flexsurfer/reflex';
// Debounce search input
function handleSearchInput(value: string) {
debounceAndDispatch(['search', value], 300);
}throttleAndDispatch
function throttleAndDispatch(event: EventVector, durationMs: number): voidDispatches an event immediately, then ignores subsequent dispatches for the duration.
Parameters:
event: Event vector to dispatchdurationMs: Throttle duration in milliseconds
Example:
import { throttleAndDispatch } from '@flexsurfer/reflex';
// Throttle window resize events
function handleResize() {
throttleAndDispatch(['window-resized', window.innerWidth], 100);
}clearHandlers, clearReactions, clearSubs
function clearHandlers(): void
function clearHandlers(kind: 'event' | 'fx' | 'cofx' | 'sub' | 'subDeps' | 'error'): void
function clearHandlers(kind: string, id: string): void
function clearReactions(): void
function clearReactions(id: string): void
function clearSubs(): voidClears registered handlers and reactions. Mainly used for testing and hot reloading.
TypeScript Types
TypeScript type definitions that provide compile-time safety and better developer experience when working with Reflex APIs.
Core Types
type Db<T = Record<string, any>> = T
type Id = string
type EventVector = [Id, ...any[]]
type SubVector = [Id, ...any[]]
type Effects = [string, any?][]Event Types
type EventHandler<T = Record<string, any>> = (
coeffects: CoEffects<T>,
...params: any[]
) => Effects | void
interface CoEffects<T = Record<string, any>> {
event: EventVector
draftDb: Draft<Db<T>>
[key: string]: any
}
type ErrorHandler = (
originalError: Error,
reflexError: Error & { data: any }
) => voidSubscription Types
type SubHandler = (...values: any[]) => any
type SubDepsHandler = (...params: any[]) => SubVector[]Effect/Co-effect Types
type EffectHandler = (value: any) => void
type CoEffectHandler<T = Record<string, any>> = (
coeffects: CoEffects<T>,
value?: any
) => CoEffects<T>Interceptor Types
interface Interceptor<T = Record<string, any>> {
id: string
before?: (context: Context<T>) => Context<T>
after?: (context: Context<T>) => Context<T>
comment?: string
}
interface Context<T = Record<string, any>> {
coeffects: CoEffects<T>
effects: Effects
newDb: Db<T>
patches: any[]
queue: Interceptor<T>[]
stack: Interceptor<T>[]
originalException: boolean
}Utility Types
interface DispatchLaterEffect {
ms: number
dispatch: EventVector
}
interface Watcher<T> {
callback: (v: T) => void
componentName: string
}Error Handling
Reflex provides comprehensive error handling:
- Event Error Handler: Catch exceptions in event processing chain
- Effect Error Handling: Effects should handle their own errors
- Validation: Invalid events, effects, and subscriptions are logged as warnings
Performance Considerations
- Subscriptions: Automatically memoized and reactive
- Events: Processed asynchronously to prevent blocking
- Tracing: Can be enabled for performance monitoring
- Debouncing/Throttling: Built-in utilities for high-frequency events
Migration Guide
From Redux
// Redux
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 }
}
});
// Reflex
initAppDb({ counter: 0 });
regEvent('increment', ({ draftDb }) => {
draftDb.counter += 1;
});
regSub('counter');From Zustand
// Zustand
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}));
// Reflex
initAppDb({ count: 0 });
regEvent('increment', ({ draftDb }) => {
draftDb.count += 1;
});
regSub('count');