Decorators and Utilities
Decorators and utility functions for managing disposable resources and memory cleanup.
@AutoDispose
Class decorator that implements automatic disposal for classes that implement Disposable.
Automatically detects properties that implement Disposable (have Symbol.dispose) and
calls [Symbol.dispose]() on them when the instance is disposed.
Auto-detection: Automatically finds all properties that implement Disposable (have Symbol.dispose).
This is safer than checking for dispose() method because Symbol.dispose is BY DESIGN
a marker for resources that need disposal (part of TC39 Explicit Resource Management).
Important: Classes decorated with @AutoDispose must implement the Disposable interface.
This ensures type safety and allows TypeScript to properly infer that the class implements Disposable.
Behavior:
- If the class already implements a
[Symbol.dispose]()method, it will be called first (for custom cleanup) - Then, all properties that implement
Disposable(haveSymbol.dispose) will be automatically disposed - Properties marked with
@IgnoreDisposableare excluded from auto-disposal
This allows classes to implement Disposable without needing inheritance, avoiding issues with single inheritance.
Note:
Decorators only work with classes. If you prefer plain objects, use makeDisposableObject instead (see below).
@AutoDispose
class CounterManager implements Disposable {
store = new ReactiveStore({ count: new ReactiveValue(0) });
// store is automatically detected and disposed (has Symbol.dispose)
[Symbol.dispose](): void {
// Custom cleanup logic if needed
// @AutoDispose automatically disposes store after this
}
}
@AutoDispose
class UserManager extends SomeOtherBaseClass implements Disposable {
store = new ReactiveStore({ user: new ReactiveValue(null) });
// store is automatically detected and disposed
[Symbol.dispose](): void {
// Custom cleanup logic
// @AutoDispose will automatically dispose store after this
}
}
Plain object alternative (using makeDisposableObject):
import { makeDisposableObject } from "@dxbox/use-less-react/classes";
function createCounterManager() {
return makeDisposableObject({
count: new ReactiveValue(0),
increment() {
this.count.set((c) => c + 1);
},
});
// Automatically detects 'count' as disposable and adds dispose() method
}
// Usage with useDisposable
function Component() {
const manager = useDisposable(() => createCounterManager());
// manager is automatically disposed on unmount
}
Excluding properties from auto-disposal:
function createManager() {
return makeDisposableObject(
{
store: new ReactiveStore({...}),
meta: {
user: {
[Symbol.dispose]: () => {} // Has Symbol.dispose but we want to handle it separately
}
}
},
{ exclude: ['meta.user'] }
);
}
@IgnoreDisposable
Property decorator to mark a property as NOT automatically disposable.
Use this decorator when a property implements Disposable but should NOT
be automatically disposed by @AutoDispose. The property will be excluded
from automatic disposal, allowing you to manage its lifecycle manually.
class CustomResource implements Disposable {
[Symbol.dispose](): void {
// Custom cleanup
}
}
@AutoDispose
class Manager implements Disposable {
store = new ReactiveStore({ count: new ReactiveValue(0) }); // Auto-disposed
@IgnoreDisposable
sharedResource = new CustomResource(); // Won't be auto-disposed
[Symbol.dispose](): void {
// store is auto-disposed, but sharedResource is not
// You can manually dispose sharedResource here if needed
}
}
Note: @AutoDispose automatically detects all properties that implement Disposable (have Symbol.dispose).
You don't need to mark properties explicitly - they are detected automatically. Use @IgnoreDisposable only when you want to exclude a property from auto-disposal.
makeDisposableObject
Makes a plain object disposable by automatically detecting properties with Symbol.dispose and adding a [Symbol.dispose]() method that calls [Symbol.dispose]() on all detected properties.
This is the plain object equivalent of @AutoDispose for classes.
Auto-detection: Automatically finds all properties that implement Disposable (have Symbol.dispose). Recursively checks all levels of nesting (not just first-level properties).
Exclude: Use the exclude option to exclude specific properties from disposal, even if they implement Disposable. Supports nested paths using dot notation (e.g., "meta.user").
function makeDisposableObject<T extends object>(
obj: T,
options?: makeDisposableObjectOptions,
): T & Disposable;
interface makeDisposableObjectOptions {
exclude?: string[]; // Array of property paths to exclude (e.g., "meta.user")
}
Example
import { makeDisposableObject } from "@dxbox/use-less-react/classes";
import { useDisposable } from "@dxbox/use-less-react/client";
function createCounterManager() {
return makeDisposableObject({
store: new ReactiveStore({ count: new ReactiveValue(0) }),
computed: new ComputedValue(
() => this.store.values.count.get() * 2,
[this.store.values.count],
),
increment() {
this.store.values.count.set((c) => c + 1);
},
});
// Automatically detects 'store' and 'computed' as disposable
}
function Component() {
const manager = useDisposable(() => createCounterManager());
// manager is automatically disposed on unmount
}
Excluding nested properties
function createManager() {
return makeDisposableObject({
store: new ReactiveStore({...}),
meta: {
user: {
[Symbol.dispose]: () => {} // Has Symbol.dispose but we want to handle it separately
},
deep: {
nested: {
disposable: new ReactiveValue(0) // Will be auto-disposed
}
}
}
}, { exclude: ['meta.user'] });
// 'store' and 'meta.deep.nested.disposable' will be disposed
// 'meta.user' will be excluded
}
Recursive detection
The function recursively searches through all nested objects to find disposable properties:
function createManager() {
return makeDisposableObject({
topLevel: new ReactiveStore({...}),
nested: {
disposable: new ReactiveValue(0),
deep: {
nested: {
disposable: new ComputedValue(() => 10, [])
}
}
}
});
// All three disposable properties will be automatically detected and disposed
}
EventSubscription
Class that wraps event handler registration and automatically unsubscribes on dispose. Used with @AutoDispose to automatically clean up event subscriptions.
Important: EventSubscription is only for event subscriptions (like eventBus handlers), never for monitoring reactive instances. For monitoring, use useMonitor* hooks in React components or monitor.*.track() for global/Node instances.
class EventSubscription implements Disposable {
constructor(registerCallback: () => () => void);
[Symbol.dispose](): void;
get disposed(): boolean;
}
Example
@AutoDispose
class UserManager implements Disposable {
private eventSubscriptions = new EventSubscription(() => {
return eventBus.registerLocalHandler("UserCreated", (event) => {
// Handle event
});
});
// eventSubscriptions is automatically detected and disposed (has Symbol.dispose)
[Symbol.dispose](): void {
// Custom cleanup logic if needed
}
}
For multiple subscriptions:
private eventSubscriptions = new EventSubscription(() => {
const unsub1 = eventBus.registerLocalHandler("UserCreated", handler1);
const unsub2 = eventBus.registerLocalHandler("UserDeleted", handler2);
return () => {
unsub1();
unsub2();
};
});
// Automatically detected and disposed by @AutoDispose
Disposable
Interface for objects that can be disposed to clean up resources.
Uses TypeScript's native Disposable interface with Symbol.dispose (TC39 Explicit Resource Management).
interface Disposable {
[Symbol.dispose](): void;
}
Objects implementing this interface should ensure that [Symbol.dispose]() is idempotent (safe to call multiple times).