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();