Experimental rewrite of React DevTools extension for debugging React applications with improved performance and multi-root support
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
The Frontend Store is the central state management system for the DevTools UI that maintains the element tree, handles bridge communication, manages profiling data, and provides event-driven updates to React components.
Central state management class that extends EventEmitter for the DevTools frontend.
/**
* Central state management for DevTools frontend
* Manages element tree, profiling data, and bridge communication
*/
class Store extends EventEmitter {
/**
* Create a new store instance
* @param bridge - Bridge for backend communication
* @param config - Optional configuration settings
*/
constructor(bridge: Bridge, config?: Config);
/** Current profiling cache instance */
get profilingCache(): ProfilingCache;
/** Number of elements in the tree */
get numElements(): number;
/** Current tree revision number */
get revision(): number;
/** Array of root element IDs */
get roots(): $ReadOnlyArray<number>;
/** Whether profiling is supported */
get supportsProfiling(): boolean;
/** Whether reload and profile is supported */
get supportsReloadAndProfile(): boolean;
/** Whether profiling is currently active */
get isProfiling(): boolean;
/** Whether profiling data is available */
get hasProfilingData(): boolean;
}
interface Config {
/** Enable reload and profile functionality */
supportsReloadAndProfile?: boolean;
/** Enable profiling functionality */
supportsProfiling?: boolean;
}Usage Examples:
import Store from "react-devtools-experimental/src/devtools/store";
import Bridge from "react-devtools-experimental/src/bridge";
// Create store with profiling support
const config = {
supportsProfiling: true,
supportsReloadAndProfile: true
};
const store = new Store(bridge, config);
// Listen for store updates
store.on('tree-updated', () => {
console.log(`Tree updated, ${store.numElements} elements`);
});
// Check profiling status
if (store.supportsProfiling) {
console.log(`Profiling active: ${store.isProfiling}`);
console.log(`Has profiling data: ${store.hasProfilingData}`);
}Methods for accessing and querying elements in the DevTools tree.
/**
* Get element at specific index in the flattened tree
* @param index - Zero-based index in the tree
* @returns Element data or null if not found
*/
getElementAtIndex(index: number): Element | null;
/**
* Get element ID at specific index
* @param index - Zero-based index in the tree
* @returns Element ID or null if not found
*/
getElementIDAtIndex(index: number): number | null;
/**
* Get element by its unique ID
* @param id - Element ID to look up
* @returns Element data or null if not found
*/
getElementByID(id: number): Element | null;
/**
* Get index of element with specific ID
* @param id - Element ID to find index for
* @returns Index in tree or null if not found
*/
getIndexOfElementID(id: number): number | null;Usage Examples:
// Access elements by index
const firstElement = store.getElementAtIndex(0);
console.log('First element:', firstElement);
// Find element by ID
const elementID = 42;
const element = store.getElementByID(elementID);
if (element) {
console.log(`Element ${elementID}:`, element.displayName);
}
// Get element index for scrolling
const elementIndex = store.getIndexOfElementID(elementID);
if (elementIndex !== null) {
scrollToIndex(elementIndex);
}
// Iterate through all elements
for (let i = 0; i < store.numElements; i++) {
const element = store.getElementAtIndex(i);
if (element) {
console.log(`${element.displayName} (depth: ${element.depth})`);
}
}Methods for getting information about renderers and roots.
/**
* Get renderer ID for a specific element
* @param id - Element ID
* @returns Renderer ID or null if not found
*/
getRendererIDForElement(id: number): number | null;
/**
* Get root ID for a specific element
* @param id - Element ID
* @returns Root ID or null if not found
*/
getRootIDForElement(id: number): number | null;Usage Examples:
// Get renderer information for element
const elementID = 42;
const rendererID = store.getRendererIDForElement(elementID);
const rootID = store.getRootIDForElement(elementID);
console.log(`Element ${elementID}:`);
console.log(` Renderer: ${rendererID}`);
console.log(` Root: ${rootID}`);
// Use for backend operations
if (rendererID && rootID) {
bridge.send('inspect-element', {
id: elementID,
rendererID: rendererID
});
}Methods for controlling performance profiling sessions.
/**
* Start performance profiling session
* Sends command to backend to begin collecting performance data
*/
startProfiling(): void;
/**
* Stop performance profiling session
* Sends command to backend to end data collection
*/
stopProfiling(): void;Usage Examples:
// Start profiling session
if (store.supportsProfiling && !store.isProfiling) {
store.startProfiling();
console.log('Profiling started');
}
// Stop profiling session
if (store.isProfiling) {
store.stopProfiling();
console.log('Profiling stopped');
}
// Listen for profiling status changes
store.on('profiling-status-changed', (isProfiling) => {
console.log(`Profiling ${isProfiling ? 'started' : 'stopped'}`);
updateProfilingUI(isProfiling);
});Key events emitted by the store for UI updates and data synchronization.
// Core store events
'tree-updated' // Element tree has changed
'element-selected' // Element selection changed
'profiling-status-changed' // Profiling status changed
'profiling-data-available' // New profiling data received
'bridge-connected' // Backend bridge connected
'bridge-disconnected' // Backend bridge disconnected
'store-reset' // Store state resetUsage Examples:
// Listen for tree updates
store.on('tree-updated', () => {
// Re-render tree UI
updateElementTree();
});
// Listen for element selection
store.on('element-selected', (elementID) => {
// Update selection UI
highlightSelectedElement(elementID);
// Load element details
loadElementInspection(elementID);
});
// Listen for profiling data
store.on('profiling-data-available', () => {
// Update profiler UI
if (store.hasProfilingData) {
showProfilingResults();
}
});
// Handle bridge disconnection
store.on('bridge-disconnected', () => {
showDisconnectedState();
});Internal methods for processing bridge messages and operations.
/**
* Handle operations from bridge
* Internal method that processes tree operations from backend
* @param operations - Serialized operations data
*/
onBridgeOperations(operations: Uint32Array): void;
/**
* Handle profiling status updates
* @param isProfiling - Current profiling status
*/
onProfilingStatus(isProfiling: boolean): void;
/**
* Handle bridge shutdown
* Cleans up resources when backend disconnects
*/
onBridgeShutdown(): void;Utility methods for debugging and development.
/**
* Print element tree to console for debugging
* Development/debug method to visualize tree structure
*/
__printTree(): void;Usage Examples:
// Debug tree structure
if (process.env.NODE_ENV === 'development') {
store.__printTree();
}The primary data structure representing React elements in the DevTools tree.
interface Element {
/** Unique element identifier */
id: number;
/** Parent element ID (0 for roots) */
parentID: number;
/** Array of child element IDs */
children: Array<number>;
/** Element type classification */
type: ElementType;
/** Display name for the element */
displayName: string | null;
/** React key prop */
key: number | string | null;
/** Owner element ID */
ownerID: number;
/** Nesting depth in tree */
depth: number;
/** Number of descendants */
weight: number;
}
/** Element type constants */
const ElementTypeClass = 1;
const ElementTypeFunction = 2;
const ElementTypeContext = 3;
const ElementTypeForwardRef = 4;
const ElementTypeMemo = 5;
const ElementTypeOtherOrUnknown = 6;
const ElementTypeProfiler = 7;
const ElementTypeRoot = 8;
const ElementTypeSuspense = 9;
type ElementType = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;Usage Examples:
// Process element based on type
const element = store.getElementByID(42);
if (element) {
switch (element.type) {
case ElementTypeClass:
console.log(`Class component: ${element.displayName}`);
break;
case ElementTypeFunction:
console.log(`Function component: ${element.displayName}`);
break;
case ElementTypeContext:
console.log(`Context: ${element.displayName}`);
break;
default:
console.log(`Other element: ${element.displayName}`);
}
// Access element hierarchy
console.log(`Parent: ${element.parentID}`);
console.log(`Children: ${element.children.length}`);
console.log(`Depth: ${element.depth}`);
}Using the store with React components and hooks:
import { useContext, useEffect, useState } from 'react';
import { StoreContext } from 'react-devtools-experimental/src/devtools/views/context';
function ElementTree() {
const store = useContext(StoreContext);
const [revision, setRevision] = useState(store.revision);
useEffect(() => {
const handleTreeUpdate = () => {
setRevision(store.revision);
};
store.on('tree-updated', handleTreeUpdate);
return () => store.off('tree-updated', handleTreeUpdate);
}, [store]);
// Render tree based on current revision
return (
<div>
{store.roots.map(rootID => (
<ElementNode key={rootID} elementID={rootID} store={store} />
))}
</div>
);
}The store integrates with React Suspense for async profiling data:
import { Suspense } from 'react';
function ProfilingResults() {
const store = useContext(StoreContext);
if (!store.hasProfilingData) {
return <div>No profiling data available</div>;
}
return (
<Suspense fallback={<LoadingSpinner />}>
<ProfilingDataDisplay cache={store.profilingCache} />
</Suspense>
);
}
function ProfilingDataDisplay({ cache }) {
// This will suspend if data is not ready
const profilingData = cache.read();
return (
<div>
<h3>Profiling Results</h3>
<ProfilingChart data={profilingData} />
</div>
);
}Proper setup and cleanup of the store:
function createDevToolsStore(bridge, config) {
const store = new Store(bridge, config);
// Set up error handling
store.on('error', (error) => {
console.error('Store error:', error);
});
// Set up cleanup
const cleanup = () => {
store.removeAllListeners();
store.onBridgeShutdown();
};
// Cleanup on page unload
window.addEventListener('beforeunload', cleanup);
return { store, cleanup };
}
// Usage
const { store, cleanup } = createDevToolsStore(bridge, config);
// Later cleanup
cleanup();