Skip to main content
Version: v1.0

DevTools

The DevTools system provides monitoring and debugging capabilities for reactive instances. It allows you to track changes, inspect state, and debug your application in real-time.

DevToolsProvider

React component that automatically initializes DevTools. Use this in your app's root to set up DevTools without manual initialization.

import { DevToolsProvider } from "@dxbox/use-less-react/devtools";

Props:

  • children: ReactNode - The child components to render
  • disabled?: boolean - If true, DevTools will not be initialized. Defaults to false.

Examples:

Disable in production:

// Next.js App Router (app/layout.tsx)
import { DevToolsProvider } from "@dxbox/use-less-react/devtools";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<DevToolsProvider disabled={process.env.NODE_ENV === "production"}>
{children}
</DevToolsProvider>
</body>
</html>
);
}

Conditional based on environment variable:

<DevToolsProvider disabled={process.env.NEXT_PUBLIC_ENABLE_DEVTOOLS !== "true"}>
{children}
</DevToolsProvider>

Note: The disabled prop gives you full control over when DevTools are initialized. The most common pattern is to disable them in production using disabled={process.env.NODE_ENV === "production"}.

Initialization

Before using DevTools, you need to initialize them once at application startup:

Client-side (browser):

Next.js Pages Router (_app.tsx):

// pages/_app.tsx
import type { AppProps } from "next/app";
import { DevToolsProvider } from "@dxbox/use-less-react/devtools";

function MyApp({ Component, pageProps }: AppProps) {
return (
<DevToolsProvider>
<Component {...pageProps} />
</DevToolsProvider>
);
}

Next.js App Router (layout.tsx):

// app/layout.tsx
"use client"; // Required when using DevToolsProvider in App Router

import { DevToolsProvider } from "@dxbox/use-less-react/devtools";

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<DevToolsProvider>{children}</DevToolsProvider>
</body>
</html>
);
}

Note: In Next.js App Router, layout.tsx is a Server Component by default. Since DevToolsProvider is a Client Component, you need to add "use client" at the top of the file when using it. Alternatively, you can create a separate client component wrapper.

Server-side (Node.js, Next.js SSR):

import { initDevTools } from "@dxbox/use-less-react/devtools";

// Initialize without console API
initDevTools();

Note: Monitor functions (monitor.reactiveValue, etc.) work even without calling initDevTools() because they automatically create the hook singleton. However, calling initDevTools() ensures the store and stat tracking are properly initialized.

Important: client-side, use DevToolsProvider which automatically passes skipBrowserConsoleAPI: false to enable devtools in console

Monitoring in React Components

Recommended approach: Use the dedicated React hooks

For React components, use the useMonitor* hooks which handle cleanup automatically:

import { useMonitorReactiveValue } from "@dxbox/use-less-react/devtools";
import { ReactiveValue } from "@dxbox/use-less-react/classes";
import { useDisposable } from "@dxbox/use-less-react/client";

function Counter() {
// Use useDisposable for component-scoped instances
const count = useDisposable(() => new ReactiveValue(0));

// Use the hook - cleanup is automatic!
useMonitorReactiveValue(count, { name: "Count" });

return <div>{count.get()}</div>;
}

Available hooks:

  • useMonitorReactiveValue(value, options) - Monitor ReactiveValue
  • useMonitorReactiveStore(store, options) - Monitor ReactiveStore
  • useMonitorReactiveObject(object, options) - Monitor ReactiveObject
  • useMonitorComputedValue(computed, options) - Monitor ComputedValue
  • useMonitorReactiveReference(reference, options) - Monitor ReactiveReference
  • useMonitorFSM(fsm, options) - Monitor FSM
  • useMonitorMemento(caretaker, options) - Monitor Memento

Important:

  • For reactive instances created inside React components: Always use useMonitor* hooks - they handle cleanup automatically and prevent memory leaks
  • For global instances: Use monitor.*.track() directly (no cleanup needed)
  • Reactive instances created in components must be properly disposed using useDisposable

Monitoring Global Instances

For global or singleton instances created outside React components, call monitor.* functions directly when the instance is created. These instances typically live for the entire application lifetime and don't need cleanup.

Example: Global store

// stores/user-store.ts
import { ReactiveStore } from "@dxbox/use-less-react/classes";
import { monitor } from "@dxbox/use-less-react/devtools";

// Global singleton store (outside React components)
export const userStore = new ReactiveStore({
name: new ReactiveValue(""),
email: new ReactiveValue(""),
});

// Monitor it directly when the module loads
monitor.reactiveStore.track(userStore, { name: "UserStore" });

Important distinction:

  • Global instances (created outside React components): Use new ReactiveValue() or new ReactiveStore() directly, then monitor them. No cleanup needed as they live for the app lifetime.
  • Component-scoped instances: Always use useDisposable(() => new ReactiveValue()) or useDisposable(() => new ReactiveStore()) to ensure proper cleanup, and use useMonitor* hooks to monitor them.

Example: Server-side monitoring

// api/user.ts (Next.js API route)
import { ReactiveStore } from "@dxbox/use-less-react/classes";
import { monitor } from "@dxbox/use-less-react/devtools";

// Create and monitor server-side
const serverStore = new ReactiveStore({
cache: new ReactiveValue({}),
});

monitor.reactiveStore.track(serverStore, { name: "ServerCache" });

Monitoring Hooks (React Components)

Dedicated React hooks for monitoring reactive instances in components. These hooks handle cleanup automatically and are the recommended approach for React components.

useMonitorReactiveValue

Monitor a ReactiveValue instance in a React component.

function useMonitorReactiveValue<T>(
value: ReactiveValue<T> | null | undefined,
options: MonitorReactiveValueOptions,
): void;

Options:

  • name: string - Instance name (required)
  • metadata?: PlainObject - Optional metadata (must be a PlainObject - no functions, classes, etc.)

Example:

import { useMonitorReactiveValue } from "@dxbox/use-less-react/devtools";
import { ReactiveValue } from "@dxbox/use-less-react/classes";
import { useDisposable } from "@dxbox/use-less-react/client";

function Counter() {
const count = useDisposable(() => new ReactiveValue(0));

// Automatic cleanup - no useEffect needed!
useMonitorReactiveValue(count, { name: "Count" });

return <div>{count.get()}</div>;
}

useMonitorReactiveStore

Monitor a ReactiveStore instance in a React component.

function useMonitorReactiveStore<T extends Record<string, any>>(
store: ReactiveStore<T> | null | undefined,
options: MonitorReactiveStoreOptions,
): void;

Example:

import { useMonitorReactiveStore } from "@dxbox/use-less-react/devtools";
import { ReactiveStore } from "@dxbox/use-less-react/classes";
import { useDisposable } from "@dxbox/use-less-react/client";

function UserProfile() {
const userStore = useDisposable(
() =>
new ReactiveStore({
name: new ReactiveValue(""),
email: new ReactiveValue(""),
}),
);

useMonitorReactiveStore(userStore, { name: "UserStore" });

// ...
}

useMonitorReactiveObject

Monitor a ReactiveObject instance in a React component.

function useMonitorReactiveObject<T extends object | null>(
object: ReactiveObject<T> | null | undefined,
options: MonitorReactiveObjectOptions,
): void;

useMonitorComputedValue

Monitor a ComputedValue instance in a React component.

function useMonitorComputedValue<T>(
computed: ComputedValue<T> | null | undefined,
options: MonitorComputedValueOptions,
): void;

useMonitorReactiveReference

Monitor a ReactiveReference instance in a React component.

function useMonitorReactiveReference<T extends AbstractReactiveValue<unknown>>(
reference: ReactiveReference<T> | null | undefined,
options: MonitorReactiveReferenceOptions,
): void;

useMonitorFSM

Monitor an FSMContextManager instance in a React component.

function useMonitorFSM<TConfig extends Record<PropertyKey, unknown>>(
fsm: FSMContextManager<TConfig, any> | null | undefined,
options: MonitorFSMOptions,
): void;

useMonitorMemento

Monitor a MementoAbstractCaretaker instance in a React component.

function useMonitorMemento<TState, Originator, TMemento>(
caretaker:
| MementoAbstractCaretaker<TState, Originator, TMemento>
| null
| undefined,
options: MonitorMementoOptions,
): void;

When to use what:

  • React components: Always use useMonitor* hooks (Do NOT use EventSubscription for monitoring)
  • Global/Node instances: Use monitor.*.track() directly (cleanup needed if not global instance)

Important: EventSubscription is only for event subscriptions (like eventBus handlers), never for monitoring reactive instances.

Example: Conditional global monitoring

// app-config.ts
import { ReactiveValue } from "@dxbox/use-less-react/classes";
import { monitor } from "@dxbox/use-less-react/devtools";

export const appConfig = new ReactiveValue({
theme: "light",
language: "en",
});

// Only monitor in development
if (process.env.NODE_ENV === "development") {
monitor.reactiveValue.track(appConfig, { name: "AppConfig" });
}

Important: When monitoring global instances directly:

  • No cleanup needed (they live for the app lifetime)
  • Works in both client and server environments
  • Can be called at module load time or when the instance is created
  • If you need to unmonitor later, use unmonitor* functions directly

Monitor API

The monitor object provides methods to track different types of reactive instances:

Monitoring ReactiveValue

import { monitor } from "@dxbox/use-less-react/devtools";
import { ReactiveValue } from "@dxbox/use-less-react/classes";

// Global instance (outside React components)
const count = new ReactiveValue(0);

const instanceId = monitor.reactiveValue.track(count, {
name: "Counter",
metadata: { category: "UI" },
});

// Check if monitored
if (monitor.isReactiveValueMonitored(count)) {
console.log("Count is being monitored");
}

// Stop monitoring
monitor.reactiveValue.untrack(count);

Monitoring ReactiveStore

import { monitor } from "@dxbox/use-less-react/devtools";
import { ReactiveStore } from "@dxbox/use-less-react/classes";

const store = new ReactiveStore({
count: new ReactiveValue(0),
name: new ReactiveValue(""),
});

const instanceId = monitor.reactiveStore.track(store, {
name: "UserStore",
metadata: { version: "1.0" },
});

// Stop monitoring
monitor.reactiveStore.untrack(store);

Monitoring ComputedValue

import { monitor } from "@dxbox/use-less-react/devtools";
import { ComputedValue } from "@dxbox/use-less-react/classes";

const doubled = new ComputedValue(() => count.get() * 2, [count]);

const instanceId = monitor.computedValue.track(doubled, {
name: "DoubledCount",
});

// Stop monitoring
monitor.computedValue.untrack(doubled);

Monitoring FSM

import { monitor } from "@dxbox/use-less-react/devtools";

const fsm = /* your FSMContextManager */

const instanceId = monitor.fsm.track(fsm, {
name: "AuthFlow",
metadata: { type: "authentication" },
});

// Get transition history
const history = monitor.fsm.getHistory(instanceId);

// Get current state
const currentState = monitor.fsm.getCurrentState(instanceId);

// Get statistics
const stats = monitor.fsm.getStats(instanceId);

// Stop monitoring
monitor.fsm.untrack(fsm);

Monitoring Memento

import { monitor } from "@dxbox/use-less-react/devtools";

const caretaker = new MyCaretaker();

const instanceId = monitor.memento.track(caretaker, {
name: "UserHistory",
metadata: { maxHistory: 50 },
});

// Get statistics
const stats = monitor.memento.getStats(instanceId);

// Stop monitoring
monitor.memento.untrack(caretaker);

Production Warnings

Best practices for production:

if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
initDevTools();
}

Console API

The DevTools also provide a console API for debugging:

// In browser console
devtools.help(); // Show available commands
devtools.monitor.reactiveValue.track(count, { name: "Count" });
devtools.instances.getAll(); // Get all monitored instances
devtools.timeline.getEvents(); // Get timeline events