Axonpack

What a tab can draw

React Native or HTML, what crosses to the panel, and what does not.

React renders your component in the app, against a renderer that reports what it drew instead of touching a DOM. The panel builds the real elements from the changes that arrive.

That is what lets onClick call auth.signOut() directly: the handler and the app's own state are in the same place, so there is no message to write and no state to mirror. Hooks, effects and any component you compose all work, because this is React.

Two flavours of JSX

The JSX can be div and button, because the elements are made at the other end, in a browser.

<div style={{ padding: 12 }}>
  <p>pressed {count} times</p>
  <button onClick={() => setCount(count + 1)}>press</button>
</div>

It can equally be View, Text, Image, ScrollView and Pressable. Those reach the panel as the host elements React Native compiled them to, and the panel draws them with react-native-web. Yoga's defaults, the units, the text layout and the stylesheet are its job, in the page where it has a real document.

<View style={{ padding: 12, gap: 8 }}>
  <Text>pressed {count} times</Text>
  <Pressable onPress={() => setCount(count + 1)}>
    <Text>press</Text>
  </Pressable>
</View>

Where the line sits

What crosses is host output, so a component whose behaviour is native code does not get that behaviour: SafeAreaView lays out but has no insets, an SVG path is an empty box, and Animated driven natively sits at its first value. Layout, text and presses come through.

This is the trade the package makes on purpose. A tab could instead be built for the web by your own Metro, which renders every React Native component perfectly, and it would then be a separate JavaScript world that cannot see one value of the app's. Reading the running app is the point, so the app is where a tab runs.

Changes cross as changes, not as a new tree, so the panel keeps the element it already had. An input holds its caret and a scrolled list stays where it was while something above it re-renders.

Text inputs

Use defaultValue, not value.

<TextInput defaultValue="" onChangeText={setQuery} />

A controlled TextInput keeps its native view in step by sending it a command, and a tab has no native view: the ref it gets is a stand-in, so React Native warns that dispatchCommand was given a ref that is not a native component, and typing throws. Uncontrolled, it never asks, and onChangeText still arrives on every keystroke.

Clear it by remounting, which is what a key that changes does. A div-and-input tab has none of this, because React and the browser own both ends of it.

What it cannot do

Touch a real element. A ref gets a stand-in, so a canvas, a measurement or a DOM library has nothing to work with, and an event arrives as { type, target: { value, checked } } rather than the event itself, with preventDefault and stopPropagation there but doing nothing.

Everything else a devtools tab usually wants needs none of that: reading the app's state, calling into it, drawing a table, filtering a list.

Copying and downloading

Copying is the one thing a handler cannot do for you, since it runs in the app and lands on the device's clipboard. Give the element COPY_ATTRIBUTE with the text instead, and the panel copies it to the computer's clipboard on the click:

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

<button {...{ [COPY_ATTRIBUTE]: curl }}>Copy as cURL</button>;

A download needs nothing special: an <a href="data:…" download="name"> is a real link in the panel, and the browser saves it.

Right-click menus

A tab that handles a right-click with onContextMenu does not get the browser's menu on top of its own. The panel cancels that for you, because your handler runs in the app and would be too late to call preventDefault itself.

The bar at the top

Every tab gets one, drawn by this package: its name, and a button that renders the component again. Nothing to add and nothing to wire up, and your component starts under it.

That button is for state a component reads but React is not watching. Everything else redraws on its own, because a tab is ordinary React.

On this page