@autotracer/react18
v1.0.0-alpha.33
Published
Tracing library to detect rerender causes and state changes
Downloads
98
Readme
@autotracer/react18
Automatic React render tracing with labeled state and prop changes.
See exactly what React is doing—component lifecycles, mount events, state updates, prop changes—all logged automatically without manual console.log statements. Your components get enhanced at build time, so you can trace complex render cycles just by calling reactTracer() in your app. Perfect for debugging development and TEST/QA environments where you need detailed insights into React's behavior.
Installation
You need two packages: the core library and a build plugin.
For Vite projects:
pnpm add @autotracer/react18
pnpm add -D @autotracer/plugin-vite-react18For Next.js / Babel projects:
pnpm add @autotracer/react18
pnpm add -D @autotracer/plugin-babel-react18Monorepo / Workspace Setup
When using @autotracer/react18 from a local workspace (e.g., in a monorepo), add Vite aliases:
// vite.config.ts
import path from "path";
export default defineConfig({
resolve: {
alias: {
"@autotracer/react18": path.resolve(
__dirname,
"path/to/packages/auto-tracer-react18"
),
},
},
// ... rest of config
});What You Get
- Automatic lifecycle tracking - Mount and update events logged automatically
- Labeled state/props - See
count: 0 → 1with readable variable names - Dormant mode - Inject hooks but activate on-demand from browser console
- Zero overhead when disabled - Complete no-op when not active
- Runtime configuration - Change tracing options on the fly
- Identical value warnings - Detects unnecessary re-renders with same values
- Framework-agnostic - Works with any React 18 setup
Perfect for Large Teams and Complex React Apps
When working on large customer solutions with many developers, understanding React component behavior in code you didn't write is crucial. React tracing gives you unprecedented visibility:
- Faster than debugging - See all component renders, state changes, and prop updates instantly instead of stepping through React internals
- Search and filter - Console output is searchable, making it easy to find when a specific state value changed or which component triggered a re-render
- Automatic relevance - Chrome's console.group collapsing means you only see the render cycles that actually happened
- Identical change detection - Automatically flags when state/props update with the same value, revealing unnecessary re-renders
- Zero instrumentation - No manual logging needed, no code changes required
- Complete React context - State transitions, prop changes, and component hierarchy all captured automatically
Example scenario: A performance issue causes unnecessary re-renders in a complex form. Instead of adding dozens of console.log statements or trying to decipher React DevTools flame graphs, activate React tracing and interact with the form. You'll instantly see which components re-render, what state/props changed (or stayed the same), and the exact sequence of events. The identical change warnings will highlight any state updates that don't actually change values — a common source of performance problems.
Function Flow Integration
Combine @autotracer/react18 with @autotracer/flow to get complete visibility into both React lifecycle and function execution:
- React lifecycle + function calls - See component renders, useEffect executions, and event handlers all interleaved in a single trace
- Event handler tracking - Trace which functions execute when users click buttons, submit forms, or trigger other events
- useEffect visibility - See exactly when effects run, what functions they call, and what state changes they trigger
- Async operation flows - Understand the complete sequence from user action → event handler → API call → state update → re-render
- State change origins - Trace the exact function call chain that caused a state update, including which effect or event handler initiated it
- Performance analysis - Identify slow functions within render cycles, event handlers, or effect cleanups
This combination gives you the most complete picture of your React application's behavior, making it trivial to understand complex interactions between user events, effects, business logic, and UI updates.
Example: A user clicks a button, triggering an event handler that fetches data, which updates state in a useEffect, causing multiple components to re-render. With both tracers active, you'll see the complete flow: the click event → handler function → fetch call → effect execution → state changes → component renders—all in one coherent console trace.
Learning React Lifecycle
Note: To fully understand React lifecycle, you need both @autotracer/react18 and @autotracer/flow working together. React tracing shows component renders and state changes, while flow tracing captures event handlers and effect function executions.
The combined tracers provide an excellent educational tool for understanding how React actually works:
- See lifecycle in action - Watch mount and update events happen in real-time as you interact with your app
- Understand render triggers - See exactly which state or prop changes cause re-renders and which don't
- Event handler execution - Observe when click handlers, form submissions, and other events fire (requires flow tracing)
- Effect execution order - See when useEffect functions run relative to rendering, including cleanup functions (requires flow tracing)
- Batching behavior - See how React batches multiple state updates into a single render
- Component hierarchy - Understand parent-child render relationships and propagation patterns
Example for learning: Build a simple counter with useEffect. Activate both tracers and click the increment button. You'll see: the click handler execution → state change (count: 0 → 1) → component re-render → effect cleanup function → effect function execution. This makes abstract React concepts concrete and observable, perfect for developers learning React or teaching others.
React DevTools Requirement
ReactTracer requires the React DevTools hook to function. You have two options:
Use the Vite or Babel plugin (recommended): The plugins automatically create a minimal DevTools hook for you. This is the easiest and most reliable approach.
Install the React DevTools browser extension: Install the React DevTools browser extension for Chrome, Firefox, or Edge.
Note: In development mode with Vite/Fast Refresh, the DevTools hook is automatically available.
If the DevTools hook is not available, you'll see this warning:
⚠️ ReactTracer: React DevTools not available. To use ReactTracer, either:
1. Install the React DevTools browser extension, OR
2. Use the @autotracer/plugin-vite-react18 or @autotracer/plugin-babel-react18 plugin (recommended)Quickstart
Note: The examples below show Vite configuration. For Next.js/Babel setup, see the Integration Guides section.
With Build Plugin (Recommended)
The build plugin automatically injects useReactTracer() hooks for labeled state/prop changes:
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { reactTracer } from "@autotracer/plugin-vite-react18";
export default defineConfig({
plugins: [
reactTracer.vite({
inject: true,
mode: "opt-out",
include: ["src/**/*.tsx"],
}),
react(),
],
});Then initialize in your app entry point:
// src/main.tsx
import { reactTracer } from "@autotracer/react18";
import { createRoot } from "react-dom/client";
import App from "./App";
// Start tracing before React renders
reactTracer();
createRoot(document.getElementById("root")!).render(<App />);Dormant Mode (TEST/QA Deployments)
For TEST/QA environments where tracing should be available on-demand but off by default, initialize with enabled: false and use the runtime control API:
// src/main.tsx
import { reactTracer } from "@autotracer/react18";
import { createRoot } from "react-dom/client";
import App from "./App";
// Initialize with tracing disabled
reactTracer({ enabled: false });
createRoot(document.getElementById("root")!).render(<App />);Activate from browser console using the runtime control API:
// Start tracing - choose either method:
window.startReactTracing(); // Convenience wrapper
window.reactTracer.start(); // Direct API
// Stop tracing when done
window.stopReactTracing();
// Check current state
window.reactTracer.isEnabled(); // Returns booleanAlternative: No initialization approach
You can also omit the reactTracer() call entirely and activate later:
// src/main.tsx - DO NOT call reactTracer()
import { createRoot } from "react-dom/client";
import App from "./App";
// No reactTracer() call - hooks are dormant no-ops
createRoot(document.getElementById("root")!).render(<App />);Activate from browser console:
// In browser console
import("@autotracer/react18").then((m) => m.reactTracer());Recommendation: The first approach (initialize disabled) is preferred because it makes the runtime control API immediately available. The second approach requires dynamic import and won't expose window.startReactTracing() until after initialization.
Both approaches give you on-demand debugging capability perfect for TEST/QA environments where you need tracing available without performance impact until activated.
Without Build Plugin
You'll still get lifecycle logs, but without variable name labels:
// src/main.tsx
import { reactTracer } from "@autotracer/react18";
import { createRoot } from "react-dom/client";
import App from "./App";
reactTracer({
includeNonTrackedBranches: true, // See all components, not just labeled ones
});
createRoot(document.getElementById("root")!).render(<App />);Options
const stop = reactTracer({
enabled: true,
includeReconciled: false,
includeSkipped: false,
showFlags: false,
internalLogLevel: "trace",
maxFiberDepth: 100,
includeNonTrackedBranches: false,
/**
* Selects the output format for the component tree.
* - "indented" (default): Traditional text-based indentation. Best for copy-pasting.
* - "console-group": Uses browser's native console.group(). Best for interactive debugging.
*/
renderer: "indented",
/**
* Filter mode for collapsing empty nodes (no changes/logs) in the component tree.
* - "none" (default): No filtering.
* - "first": Collapses only the initial sequence of empty nodes.
* - "all": Collapses all empty node sequences.
*/
filterEmptyNodes: "none",
/**
* Detects re-renders where the new value is structurally identical to the previous
* value but with a different reference (e.g. re-created arrays, objects, inline
* functions). When true a warning line is logged:
* "⚠️ State change filteredTodos (identical value): [] → []"
* Uses stable JSON stringification for deep equality (including circular markers).
*/
detectIdenticalValueChanges: true,
/**
* Custom color theme for console output.
* Overrides default colors and theme files for all 13 semantic categories.
* See THEME-FILES.md for complete theme customization guide.
*/
colors: {
definitiveRender: {
icon: "🔄",
lightMode: { text: "#1e40af" },
darkMode: { text: "#93c5fd" },
},
// ... other categories
},
});Theme Customization
Customize console output colors and icons without changing code using external theme files. Perfect for personal accessibility preferences and team standards.
Quick Start
Create a theme file in your project root:
react-theme-light.json (Light mode)
{
"definitiveRender": {
"icon": "🔄",
"lightMode": { "text": "#0066cc" }
},
"error": {
"icon": "❌",
"lightMode": { "text": "#cc0000", "background": "#fff5f5" }
}
}react-theme-dark.json (Dark mode)
{
"definitiveRender": {
"icon": "🔄",
"darkMode": { "text": "#66b3ff" }
},
"error": {
"icon": "❌",
"darkMode": { "text": "#ff6666", "background": "#3f1f1f" }
}
}Add to .gitignore to keep themes personal:
# Personal react tracer themes
*react-theme.json
*react-theme-light.json
*react-theme-dark.jsonRestart your dev server to apply changes.
Available Categories
Customize any of 13 semantic categories:
definitiveRender- Component rendered with actual changes (🔄)propChange- Rendered due to prop changes (📥)stateChange- Rendered due to state changes (📊)parentUpdate- Rendered due to parent update (⬆️)contextChange- Rendered due to context changes (🌐)hookChange- Rendered due to hook changes (🎣)forceUpdate- Rendered due to forceUpdate (⚡)noChanges- Rendered with no detected changes (⏭️)mount- First mount (🆕)unmount- Unmount (🗑️)skipped- Render skipped (⏭️)reconciled- Reconciled without DOM update (⚙️)error- Error boundary or error state (❌)
Theme Priority
Themes merge in this order (later overrides earlier):
- Built-in defaults
- Programmatic config (
reactTracer({ colors: {...} })) - Theme files (highest priority)
Complete Guide
See THEME-FILES.md for:
- Complete theme file structure and schema
- Light/dark mode configuration
- Colorblind-friendly themes
- High-contrast themes
- Troubleshooting and best practices
Removed legacy option:
- showFunctionContentOnChange: falseThe formatting no longer short-circuits functions; values are always safely stringified.
Live updates
import {
updateReactTracerOptions,
isReactTracerInitialized,
} from "@autotracer/react18";
if (isReactTracerInitialized()) {
updateReactTracerOptions({ showFlags: true });
}Runtime Control API
The React tracer exposes a global runtime control API for starting, stopping, and checking tracing status from the browser console or programmatically:
Global API:
// Start tracing
window.reactTracer.start();
// Stop tracing
window.reactTracer.stop();
// Check if tracing is enabled
window.reactTracer.isEnabled(); // Returns booleanConvenience Wrappers:
// Start tracing (equivalent to window.reactTracer.start())
window.startReactTracing();
// Stop tracing (equivalent to window.reactTracer.stop())
window.stopReactTracing();Availability:
The runtime control API is available immediately after reactTracer() initializes, regardless of whether tracing starts enabled or disabled. This means you can:
- Initialize with
reactTracer({ enabled: false })and later activate withwindow.startReactTracing() - Toggle tracing on and off during development without reloading the page
- Control tracing programmatically in TEST/QA environments
Example - Dormant mode with runtime activation:
// src/main.tsx
import { reactTracer } from "@autotracer/react18";
import { createRoot } from "react-dom/client";
import App from "./App";
// Initialize disabled, activate on demand
reactTracer({ enabled: false });
createRoot(document.getElementById("root")!).render(<App />);// Later, in browser console
window.startReactTracing(); // Activate tracing
// ... interact with app, observe traces ...
window.stopReactTracing(); // Deactivate tracingTypeScript Support:
The ReactTracerAPI type is exported for TypeScript users who need to reference the API:
import type { ReactTracerAPI } from "@autotracer/react18";
// Access typed API from window
const api: ReactTracerAPI = window.reactTracer;Inactive behavior and runtime changes
- When
reactTracer()has not been initialized orenabledisfalse, theuseReactTracer()hook is a strict no-op. It still anchors a hook position to preserve React's rules of hooks, but it does not register GUIDs, labels, or logs. - If
internalLogLevelis"trace"andenabled=truebut the tracer is not initialized, a one-time guidance message is printed:ReactTracer: useReactTracer() called while tracer not initialized. Call reactTracer() early in app startup. - Calling
stopReactTracer()restores the original DevTools hook and clears internal registries once. - Updating options at runtime with
{ enabled: false }when the tracer is active will automatically stop tracing (and clear registries). SubsequentuseReactTracer()calls remain no-ops until tracing is re-enabled and initialized again.
What you’ll see
- Component render cycle …
- State change : →
Labels come from the helper hook useReactTracer, which can be auto-injected into your components by our Babel/Vite plugins. Without injection, you still get lifecycle logs but with fewer labels.
Identical value warnings
When detectIdenticalValueChanges is true (default) a value change with a different reference but identical deep content logs a warning line with a shared icon (⚠️) and the (identical value) suffix:
│ ⚠️ State change filteredTodos (identical value): [] → []
│ ⚠️ Prop change items (identical value): [1,2,3] → [1,2,3]Distinct color/style buckets are applied internally for state vs prop identical warnings, inheriting the base theme hues.
API
Functions
reactTracer(options?): () => voidStarts tracing; returns a stop function.updateReactTracerOptions(partial): voidChanges options at runtime.isReactTracerInitialized(): booleanReturns true if tracing is currently active.useReactTracer(labels?)Helper hook returning a logger. Optional; intended for build-time injection.
Runtime Control (Global API)
After calling reactTracer(), these methods are available on the window object:
window.reactTracer.start(): voidStart React tracing.window.reactTracer.stop(): voidStop React tracing.window.reactTracer.isEnabled(): booleanCheck if tracing is currently enabled.window.startReactTracing(): voidConvenience wrapper forwindow.reactTracer.start().window.stopReactTracing(): voidConvenience wrapper forwindow.reactTracer.stop().
TypeScript Types
ReactTracerAPIType definition for the runtime control API interface.
Type Notes
- All TypeScript is strict. No casts.
- Options include bounds checking (e.g., maxFiberDepth practical range 20–1000).
Filtering: Include and Exclude Patterns
Control which components get instrumented using build plugin include and exclude patterns:
File Path Patterns
Instrument only files in specific directories:
// Vite example
reactTracer.vite({
inject: true,
mode: "opt-out",
include: ["src/**/*.tsx"], // Only instrument TSX files in src
exclude: ["**/*.spec.*", "**/*.test.*"], // Skip test files
});Path patterns use glob syntax:
src/**/*.tsx- All TSX files under src directory**/components/**- All files in any components directory**/*.spec.*- All spec files anywhere in the project
Pragma Control
Both plugins support function-level pragmas for fine-grained per-component control:
Opt-out mode (default) - All components traced except disabled:
// @trace-disable
export function MyComponent() {
return <div>Not traced</div>;
}
export function AnotherComponent() {
return <div>Traced (no pragma = default behavior)</div>;
}Opt-in mode - Only explicitly marked components traced:
// @trace
export function MyComponent() {
return <div>Traced</div>;
}
export function AnotherComponent() {
return <div>Not traced (no pragma in opt-in mode)</div>;
}Combined Filtering
Path patterns and pragmas work together. A component is instrumented only if:
- File matches
includepatterns (or no include specified) - File doesn't match
excludepatterns - Component has
// @tracepragma (opt-in mode) OR doesn't have// @trace-disable(opt-out mode)
Integration guides
Vite
Development Mode:
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { reactTracer } from "@autotracer/plugin-vite-react18";
export default defineConfig({
plugins: [
reactTracer.vite({
inject: true,
mode: "opt-out",
include: ["src/**/*.tsx"],
}),
react(),
],
});// src/main.tsx
import { reactTracer } from "@autotracer/react18";
import { createRoot } from "react-dom/client";
import App from "./App";
// Initialize before React renders
reactTracer();
createRoot(document.getElementById("root")!).render(<App />);TEST/QA Builds with Workspace Libraries:
When building for TEST/QA environments in a monorepo where your app depends on workspace libraries, enable automatic UMD loading:
// vite.config.ts
import path from "path";
const isDev = process.env.NODE_ENV === "development";
const isQA = process.env.DEPLOY_ENV === "qa";
export default defineConfig(({ mode }) => ({
resolve: {
alias: {
"@autotracer/react18": path.resolve(
__dirname,
"../../packages/auto-tracer-react18"
),
},
},
plugins: [
reactTracer.vite({
inject: isDev || isQA, // Only dev and QA, never production
mode: "opt-out",
include: ["src/**/*.tsx"],
// Enable automatic UMD loading for QA builds with workspace deps
buildWithWorkspaceLibs: isQA,
}),
react(),
],
}));Dormant Mode Option: In QA builds, you can omit the reactTracer() call in main.tsx. The hooks will be injected but dormant, and you can activate tracing from the browser console with import("@autotracer/react18").then(m => m.reactTracer()). This provides on-demand debugging without any performance overhead when inactive.
The plugin automatically handles DevTools hook creation and module resolution. See the Vite plugin README for details.
Next.js
- Add the Babel plugin (packages/@autotracer/plugin-babel-react18) to auto-inject labels in client components.
- Ensure reactTracer() executes on the client before components mount.
- Pages Router: initialize in pages/_app.tsx.
- App Router: include a minimal "use client" bootstrap component rendered first that runs reactTracer().
- SSR cannot be traced; hydration and subsequent renders are.
Internals overview
graph TD
A[Your app] -->|reactTracer| B[Attach to DevTools hook]
B --> C[Subscribe to renderer events]
C --> D[Compute diffs and labels]
D --> E[Console group + logs]The auto-tracer-inject-core with Babel/Vite plugins augments your source so useReactTracer can expose variable names and hook labels in these logs.
Security Considerations
Do Not Use in Production
React tracing should never be enabled in production environments for the following reasons:
Information Disclosure - Component state, props, and render details are logged to the browser console, potentially exposing:
- User data and personal information (PII)
- Authentication state and session details
- Business logic and application structure
- Internal component implementation details
Performance Impact - Tracing generates significant console I/O and overhead that degrades user experience, especially with complex component trees
Memory Usage - The DevTools hook and tracing infrastructure maintain references to fibers and state, increasing memory consumption
Recommended Setup
- Local Development - Enable tracing for immediate feedback during development
- TEST/QA Environments - Enable tracing for on-demand debugging
- Production - Disable tracing completely using environment-based configuration:
// vite.config.ts
import { defineConfig } from "vite";
import { reactTracer } from "@autotracer/plugin-vite-react18";
const isDev = process.env.NODE_ENV === "development";
const isQA = process.env.DEPLOY_ENV === "qa";
export default defineConfig({
plugins: [
reactTracer.vite({
inject: isDev || isQA, // Disabled in production
mode: "opt-out",
include: ["src/**/*.tsx"],
}),
],
});// src/main.tsx
import { reactTracer } from "@autotracer/react18";
// Only initialize in non-production
if (import.meta.env.MODE !== "production") {
reactTracer();
}Performance Considerations
Overhead Breakdown
With useReactTracer() hooks injected:
- Hook execution: ~1-2μs per component render
- State/prop diffing: ~5-10μs per tracked value
- Console output: ~50-100μs per logged change
- DevTools hook subscription: ~10-20μs per fiber commit
Without injection (includeNonTrackedBranches: true):
- Much higher overhead as all components are processed
- Console output can slow renders significantly with large component trees
Recommendations
- Use build plugins - Selective instrumentation via
include/excludepatterns minimizes overhead - Avoid
includeNonTrackedBranches: truein large apps - This option processes every component and can severely impact performance - Use
renderer: "console-group"- Browser-native grouping is faster than manual indentation - Consider
filterEmptyNodes- Reduces console noise and I/O overhead - Disable in production - Always use environment checks to prevent tracing in production builds
Troubleshooting
No logs appearing
Check the following:
- Verify
reactTracer()runs before React renders and that you're in a client context. - If you're not using an injection plugin, enable
includeNonTrackedBranches: truein options to see components without labels. ⚠️ Warning: This tends to be very taxing on your system because it may render a huge amount of formatted data in every render cycle. - Ensure the injection plugin is configured if you want labeled state/prop changes for your own components mainly.
Guidance message about initialization
You're calling useReactTracer() (directly or via injection) but haven't called reactTracer() yet. Initialize early in your app's startup, or disable tracing via { enabled: false }.
"React DevTools not available" warning
Either install the React DevTools browser extension or use the @autotracer/plugin-vite-react18 or @autotracer/plugin-babel-react18 plugin, which automatically handles this for you.
Unlabeled logs
Ensure the injection plugin is configured and active in your build. For non-tracked components there might be "unknown" fields that doesn't have deducible fields names. This is normal.
Too verbose
Tweak options like includeReconciled/includeSkipped and filterEmptyNodes. The default settings will only log your components and filter most irrelevant non-running code.
Repeated identical value warnings
This is a warning that your code potentially is causing unneccesary rerenders.
Consider memoization (React.memo, useMemo, useCallback) for your return value, or stable selectors.
"State change unknown" in third-party components
This is expected behavior. Third-party libraries (e.g., MUI's ButtonBase) use internal state hooks that aren't labeled because they're not transformed by the build plugin. The tracer correctly detects the state changes but can't provide meaningful labels since it only transforms your source code, not node_modules.
License
MIT
