Axonpack

Reference

Every export, and every option each one takes.

ReactNativeDevtoolsPanel

The app's side of React Native DevTools. A single object, because an app has exactly one debugger connection. Usable immediately: anything sent before somebody opens DevTools is kept and flushed when they do, and a release build has no connection at all, so it stays quiet.

import { ReactNativeDevtoolsPanel } from "@axonpack/react-native-devtools-tab";

const tab = ReactNativeDevtoolsPanel.registerTab(options: TabOptions): RegisteredTab;

Call it once per tab, as many times as you have tabs.

TabOptions

OptionTypeDefaultWhat it is
namestringrequiredThe label in the tab strip, and what the tab's id is built from
componentComponentTyperequiredWhat the tab draws, under the bar this package puts above it
iconstringthe Axonpack markA symbol shown after the tab's name

icon is text rather than an image because React Native DevTools' own icon slots take an element, and every way of putting one there loses its drawing. Any character works, so an emoji does too.

An id is derived from name rather than written by hand, and numbered if two tabs share a name. It is also the DevTools panel's own id, so a reload lands on the tab that is already open instead of leaving a dead one in the strip beside a new one.

RegisteredTab

What registerTab returns.

MemberTypeWhat it does
focus()() => voidShows this tab in a DevTools window that is already open, and in the next one that connects

Call focus() just before opening DevTools from the app, for example with DevSettings's openDebugger, and the window lands on the tab instead of on Console. On iOS nothing else can choose the panel: openDebugger takes no argument, and DevTools picks its starting panel before any tab of yours has been added.

import { TurboModuleRegistry } from "react-native";

const tab = ReactNativeDevtoolsPanel.registerTab({
  name: "Session",
  component: Session,
});

// Later, from a button in the app, in a debug build:
tab.focus();
// React Native's own DevSettings: openDebugger is what the dev menu's Open DevTools calls.
TurboModuleRegistry.get<{ openDebugger?: () => void }>(
  "DevSettings",
)?.openDebugger?.();

COPY_ATTRIBUTE

import { COPY_ATTRIBUTE } from "@axonpack/react-native-devtools-tab";

<button {...{ [COPY_ATTRIBUTE]: text }}>Copy</button>;

An element carrying it has its value copied to the clipboard of the computer DevTools runs on when it is clicked, and a short note in the tab says whether that worked. Any onClick on the element still runs, after the copy.

A handler runs in the app, so a copy written the ordinary way, with the app's own clipboard module, lands on the device's clipboard, which a phone does not share with the computer. This is the way to put text on the computer's.

withReactNativeDevtoolsTab

The Metro side. One line in metro.config.js, and the tab is served.

const {
  withReactNativeDevtoolsTab,
} = require("@axonpack/react-native-devtools-tab/metro");

withReactNativeDevtoolsTab(config, options?);

Options

OptionTypeDefaultWhat it is
frontendPathstringdiscoveredWhere React Native DevTools' own files are, for a layout this cannot work out for itself

See Troubleshooting for how to find that path and when you need it.

How it works

React Native DevTools is a Chrome DevTools frontend fork, served by @react-native/dev-middleware. This package serves that same frontend from its own route, adds a nonce to the page's CSP and injects one script, and that script re-imports the frontend's own modules to reach InspectorView.addPanel. The tab's body is an iframe, and it reaches the device over the debugger connection the frontend already has, tagged with the tab's own id so it never crosses React DevTools' own traffic.

Inside that iframe is a custom React reconciler's other half. The app's React commits produce a list of changes (create this node, move that one, set these props) and the page replays them onto real elements. A function prop cannot be sent, so each is swapped for the position it sits at in the tree; the page calls back with that position and the app runs the closure it stands for.

On this page