Skip to main content
Version: v1.0

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 (have Symbol.dispose) will be automatically disposed
  • Properties marked with @IgnoreDisposable are 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).