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.