createDevtoolsClient
Every configuration option, and every method on the client it returns.
import { createDevtoolsClient } from '@axonpack/expo-devtools';
export const devtools = createDevtoolsClient(config?);Call it once, at module scope, and export the instance. Everything else hangs off it. Every option is optional, and the defaults are what most apps want.
Top level
| Option | Type | Default | What it does |
|---|---|---|---|
defaultTheme | ThemeId | 'light' | Which theme the panel opens with: a built-in or one of yours. |
themes | Record<string, ThemeConfig> | undefined | Your own themes: a base to inherit and the tokens to override. |
webviewSources | readonly string[] | undefined | Names of <WebView />s allowed to report in, for both the Network and Console tabs. |
webviewSources uses a const type parameter, so the literal names flow into the WebView helpers'
parameter types: passing an undeclared name is a compile error, and at runtime a message from an
undeclared source is dropped.
Network
The switches name the kind of traffic, not the mechanism that carried it. A request is a request
whether it went out through fetch, through XMLHttpRequest, from a JSI client or from inside a page.
| Option | Type | Default | What it does |
|---|---|---|---|
network.http | boolean | true | Capture plain requests, by whatever transport they left on. Off also means no phase timing. |
network.websocket | boolean | true | Capture WebSocket connections and their messages, the app's own and a page's. |
network.sse | boolean | true | Capture server-sent event streams and their events, whichever client opened them. |
network.disabledByDefault | boolean | false | Open the Network tab paused. |
With sse off, the app's own stream is still recognised as one — its endless body has to be, or it
would be read as a response — so the row remains and only the events are dropped. A page's stream has no
request underneath it that anything here can see, so that one disappears entirely.
Console
| Option | Type | Default | What it does |
|---|---|---|---|
console.capture | boolean | true | Mirror console.* into the Console tab, including from declared WebViews. |
console.repl | boolean | true | Show the > prompt. |
console.context | Record<string, unknown> | undefined | Extra names an expression can use, for example { store, queryClient }. |
console.disabledByDefault | boolean | false | Open the Console tab paused. The prompt still works. |
console.repl is not gated on __DEV__
It defaults to true in every build. Once init() has run the prompt is there, including in a
release build, where it runs whatever is typed into it. Guard your init() call, or set
console: { repl: false }.
Performance
| Option | Type | Default | What it does |
|---|---|---|---|
performance.sampleIntervalMs | number | 1000 | How often memory is sampled. Each read crosses into the engine, so keep it coarse. |
performance.longTaskThresholdMs | number | 150 | Only keep tasks that blocked the JS thread at least this long. |
performance.interactionThresholdMs | number | 100 | Only keep interactions at least this long, event to next paint. |
performance.historySize | number | 120 | How many memory samples, long tasks, user timings and interactions are kept. |
performance.disabledByDefault | boolean | true | Open the Performance tab paused. Defaults to on, since measuring costs something. |
Storage
| Option | Type | Default | What it does |
|---|---|---|---|
storage.adapters | StorageAdapterDefinition[] | undefined | The stores the Storage tab can see. Nothing is discovered automatically. |
storage.maxKeys | number | 1000 | Keys read per store before the tab stops and says how many it skipped. |
storage.readOnly | boolean | false | Blanket read-only default; an individual adapter can still set its own. |
See Storage adapters for how to build one.
Crash
Crash capture is the only part of this package that can run without init().
| Option | Type | Default | What it does |
|---|---|---|---|
crash.enabled | boolean | true | Capture at all. |
crash.enableWhileDevtoolsDisabled | boolean | false | Install the handlers when the client is constructed, so crashes are reported without init(). |
crash.handlers.jsErrors | boolean | true | The ErrorUtils global handler — fatal and non-fatal JS errors. |
crash.handlers.unhandledRejections | boolean | true | Unhandled promise rejections, via the Hermes rejection tracker. |
crash.handlers.nativeExceptions | boolean | true | Uncaught Java/Kotlin and Objective-C exceptions, via the native module. |
crash.popupDetail | 'auto' | 'full' | 'compact' | 'auto' | Which sheet a crash opens. 'auto' is the full sheet when the devtools are enabled, compact when not. |
crash.disableDefaultLogBox | boolean | false | Uninstall React Native's LogBox, so a JS error is reported here and nowhere else. |
crash.breadcrumbs | boolean | true | Attach the recent console and network entries to each record. |
crash.maxBreadcrumbs | number | — | How many breadcrumbs are attached. |
crash.maxRecords | number | 25 | Reports kept in memory. |
crash.persistNonFatal | boolean | false | Also write non-fatal records to disk. |
crash.redact | (record: CrashRecord) => CrashRecord | null | undefined | Runs before the record reaches the store, the disk or onCrash. Return null to drop it. |
crash.onCrash | (record: CrashRecord) => void | undefined | Your own handler, after redact. |
Before init(), an app relying on enableWhileDevtoolsDisabled alone installs nativeExceptions only,
whatever the other two say: the JS tiers report errors the app survived, which is a developer's concern,
and the sheet there is in front of a user. A fatal JS error still arrives, because React Native turns it
into a native exception on the way to killing the process.
disableDefaultLogBox uninstalls LogBox rather than muting it, which takes the yellow warning toasts
with it — LogBox is one component and the two cannot be separated. Warnings are still captured by the
Console tab. It only does anything in development; LogBox is already an empty stub in a release build.
Client methods
| Member | What it does |
|---|---|
init() | Installs everything: the fetch/XHR patches, the console patch, the REPL context, the performance collectors, your storage adapters, and your themes. Until this runs, nothing is captured and no store is read. Call once, as early as possible. |
mark(name, options?) | Records a user-timing mark. options: { detail?, startTime? }. |
measure(name, startOrOptions?, endMark?) | Records a measure. Second argument is a start-mark name or { start?, end?, duration?, detail? }. Passing start, end and duration together throws, since they can disagree. |
clearMarks(name?) | Drops recorded marks, all of them or one name. |
clearMeasures(name?) | Drops recorded measures, all of them or one name. |
setCrashContext(context) | Extra keys attached to every crash record from here on — user id, route, feature flags. |
getWebViewInjectedJavaScriptBeforeContentLoaded(source) | The script to hand a <WebView />'s injectedJavaScriptBeforeContentLoaded. Covers both requests and console output. |
handleWebViewMessage(event) | Feed a <WebView />'s onMessage events here. Returns true when it consumed one. |
getWebViewRef(source) | A ref to attach to the <WebView />, so a throttle change reaches an already-open page. |
getWebViewUserAgent() | The current user-agent override, for the userAgent prop. |
shouldAllowWebViewRequest | For onShouldStartLoadWithRequest. Blocks navigation while Offline is on. |
networkLogStore, networkConditionsStore, consoleLogStore, storageStore, crashStore | The underlying stores, if you want to read or drive them yourself. |
User timing
devtools.mark('checkout');
await buildCart();
devtools.measure('checkout'); // measures from the mark of the same namemeasure follows the W3C User Timing signatures, and calls are
forwarded to the real performance.mark and performance.measure too, so the entries exist on the
platform timeline as well. Nothing is observed from that timeline, which is why React's own internal
measures never appear in the list.