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 renderdisabled?: boolean- Iftrue, DevTools will not be initialized. Defaults tofalse.
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 ReactiveValueuseMonitorReactiveStore(store, options)- Monitor ReactiveStoreuseMonitorReactiveObject(object, options)- Monitor ReactiveObjectuseMonitorComputedValue(computed, options)- Monitor ComputedValueuseMonitorReactiveReference(reference, options)- Monitor ReactiveReferenceuseMonitorFSM(fsm, options)- Monitor FSMuseMonitorMemento(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()ornew ReactiveStore()directly, then monitor them. No cleanup needed as they live for the app lifetime. - Component-scoped instances: Always use
useDisposable(() => new ReactiveValue())oruseDisposable(() => new ReactiveStore())to ensure proper cleanup, and useuseMonitor*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 useEventSubscriptionfor 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