@0xhex/react-xray
v0.1.0
Published
Inspect React Fiber state from the browser console after an app has rendered.
Maintainers
Readme
react-xray
Inspect React 18 applications from the browser console after they have already rendered. No extension or pre-load integration is required.
Use react-xray only on applications you own or are explicitly authorized to inspect. React Fiber is private API, so compatibility is version-specific.
// Run on an authorized page using the supported React version:
const x = ReactXRay.xray({ verbose: true });
x.tree();Why?
Existing tools like React DevTools require a browser extension. Libraries like bippy need to load before React. react-xray's verified read path works after React has already rendered: it discovers the existing root, finds components, reads props and hook state, and maps DOM elements back to components.
Verified 0.1.0 contract
The release check proves, against React 18.3, that:
- ESM and CommonJS imports expose
xray. - The browser IIFE exposes
ReactXRay.xray. - Post-render injection discovers a root and named components.
- DOM inspection and state reads return the expected component.
- State dispatch, event interception, highlighting, and cleanup work against a deterministic local fixture.
- The npm tarball works from a clean temporary consumer project.
Quick Start
The npm package is published as @0xhex/react-xray; the project and global
browser API remain named react-xray and ReactXRay.
Console (one-liner)
const s=document.createElement('script');s.src='https://unpkg.com/@0xhex/[email protected]/dist/index.iife.js';s.onload=()=>{window.x=ReactXRay.xray({verbose:true});x.tree()};document.head.append(s);CDN Script Tag
<script src="https://unpkg.com/@0xhex/[email protected]/dist/index.iife.js"></script>
<script>
const x = ReactXRay.xray({ verbose: true });
</script>npm
npm install @0xhex/react-xrayimport { xray } from '@0xhex/react-xray';
const x = xray({ verbose: true });Bookmarklet
See demo/bookmarklet.js — drag it to your bookmarks bar and click on any React page.
Reproducible local demo
npm ci
npm run build
python3 -m http.server 4173Open http://localhost:4173/demo/. The page renders React first, injects the
local IIFE afterward, and displays the discovered components and counter state.
API Reference
xray(options?)
Initialize react-xray. Returns an XRayInstance.
| Option | Type | Default | Description |
|-----------|-------------------|--------------|------------------------------------------|
| global | string \| false | '__XRAY__' | Attach instance to window[global] |
| verbose | boolean | false | Log lifecycle events to console |
Finding Components
x.find('Counter') // all components named "Counter"
x.find(/^App/) // regex matching
x.findOne('TodoItem') // first match or nullReturns ComponentInfo objects:
interface ComponentInfo {
name: string;
fiber: Fiber;
props: Record<string, any>;
hooks: HookInfo[];
element: Element | null;
key: string | null;
children: string[];
}Inspecting Elements
// Click an element in Elements panel, then:
x.inspectSelected()
// Or pass a DOM element directly:
x.inspect(document.querySelector('.my-component'))Reading State
const counter = x.findOne('Counter');
x.hooks(counter.fiber) // all hooks
x.props(counter.fiber) // current props
x.stateAt(counter.fiber, 0) // first useState valueHookInfo shape:
interface HookInfo {
index: number;
type: HookType; // 'useState' | 'useEffect' | 'useRef' | ...
value: any;
dispatch?: Function; // present for useState/useReducer
}Modifying State
const counter = x.findOne('Counter');
// Set the first useState to 42:
x.setState(counter.fiber, 0, 42);
// Experimental; may not trigger a render:
x.setProps(counter.fiber, { label: 'Modified!' });Watching Renders
Render subscriptions require a compatible DevTools hook that React already knows about; injecting only after the initial render does not create that connection retroactively.
// Watch all renders:
const unsub = x.onRender((event) => {
console.log(`${event.name} ${event.phase}d`);
});
// Watch specific component:
x.onRender('Counter', (event) => {
console.log('Counter rendered!', event.phase);
});
// Stop watching:
unsub();Intercepting Events
// Log all clicks:
x.intercept('click', (e) => {
console.log('Click intercepted:', e.target);
});
// Block all clicks:
x.intercept('click', (e) => {
console.log('Blocked click on', e.target);
return false; // stops propagation
});Component Tree
x.tree() // print full tree
x.tree(3) // max depth of 3Visual Highlighting
const counter = x.findOne('Counter');
x.highlight(counter.fiber); // default blue
x.highlight(counter.fiber, 'rgba(255,0,0,0.3)'); // custom color
x.unhighlight(); // remove allFiber Roots
x.roots() // array of root fibersCleanup
x.destroy() // remove all listeners, overlays, global referenceRecipes
1. Explore an unfamiliar React app
const x = ReactXRay.xray({ verbose: true });
x.tree();
x.find(/./).forEach(c => console.log(c.name, c.props));2. Find and modify a counter
const c = x.findOne('Counter');
console.log('Current value:', x.stateAt(c.fiber, 0));
x.setState(c.fiber, 0, 999);3. Monitor re-renders when a compatible hook is connected
x.onRender((e) => {
console.log(`[${e.phase}] ${e.name}`, e.fiber.memoizedProps);
});4. Intercept and log form submissions
x.intercept('submit', (e) => {
const form = e.target;
const data = new FormData(form);
console.table(Object.fromEntries(data));
return false; // prevent submission
});5. Highlight all instances of a component
x.find('Button').forEach(b => x.highlight(b.fiber, 'rgba(255,165,0,0.4)'));
// Later: x.unhighlight()6. Interactive element picker
document.addEventListener('click', function pick(e) {
e.preventDefault();
e.stopPropagation();
const info = x.inspect(e.target);
if (info) {
console.log(info.name, info.props, info.hooks);
x.highlight(info.fiber);
}
document.removeEventListener('click', pick, true);
}, true);7. Dump all state in the app
x.find(/./).forEach(c => {
const stateHooks = c.hooks.filter(h => h.type === 'useState');
if (stateHooks.length > 0) {
console.group(c.name);
stateHooks.forEach(h => console.log(` state[${h.index}]:`, h.value));
console.groupEnd();
}
});How It Works
react-xray uses three React internals techniques:
1. DOM → Fiber Bridge
React stamps every host DOM node with a __reactFiber$<hash> property pointing to its fiber node. We discover this key once and cache it, giving us instant fiber access from any DOM element.
2. DevTools Global Hook
React looks for window.__REACT_DEVTOOLS_GLOBAL_HOOK__ on load and calls hook.inject(renderer) + hook.onCommitFiberRoot(...) on every commit. If the hook already exists (DevTools extension), we wrap it. If not, we create one and discover existing roots from the DOM.
3. Fiber Tree Traversal
React's fiber tree is a linked list: each fiber has child (first child), sibling (next peer), and return (parent). Walking this structure gives us the full component hierarchy with props, hooks state (memoizedState linked list), and DOM references.
Compatibility
| React Version | 0.1.0 status | |---------------|--------------| | 18.3 | Verified release contract | | 19.x | Unverified | | 17.x | Unverified, best effort | | 16.8+ | Unverified, best effort | | < 16.8 | Unsupported |
Limitations
- Private API: React can change Fiber fields without a semver guarantee.
- Production builds: Hook type detection falls back to heuristics (no
_debugHookTypes). Types may be reported asunknown. - Render subscriptions: post-render root discovery supports reads, but
onRenderneeds a compatible DevTools hook that React already knows about to observe future commits. - State mutation:
setStateis best effort. It prefers the DevTools renderer API and otherwise dispatches through the hook queue. - Props mutation:
setPropsdepends on renderer support or direct Fiber mutation and is experimental. - Minified names: Production builds minify component names. Use regex patterns or inspect from DOM elements instead.
- Server components: React Server Components don't have client-side fibers.
- Strict mode: Double-rendering in dev mode may cause duplicate render events.
Development and release verification
npm ci
npx playwright install chromium
npm test
npm run release:checkSee RELEASE.md for the exact publication and independent
verification checklist. SECURITY.md documents the authorized-use boundary.
License
MIT
