npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tailglow/react-native

v0.4.0

Published

React Native SDK for Tailglow. Auto-collects JS errors, console activity, app lifecycle, and screen views via React Navigation / Expo Router integrations.

Readme

@tailglow/react-native

React Native SDK for Tailglow. Auto-collects JS errors, console activity, app lifecycle, and screen views (via React Navigation or Expo Router).

Stability

0.x is unstable. Breaking changes may ship in any minor (0.1.00.2.0) release until 1.0.

Supported runtimes

| Runtime | Floor | | ------------ | --------------------------------- | | React Native | >=0.74 | | Expo SDK | >=51 (which uses RN 0.74) | | Hermes | preferred (default since RN 0.70) | | JSC | supported |

ESM only. No CJS bundle.

Install

bun add @tailglow/react-native
# or: npm install @tailglow/react-native

# Optional, for cold-start queue persistence:
bun add @react-native-async-storage/async-storage

Usage

// App.tsx
import AsyncStorage from "@react-native-async-storage/async-storage";
import { NavigationContainer, useNavigationContainerRef } from "@react-navigation/native";
import { createAsyncStorageAdapter, Tailglow } from "@tailglow/react-native";
import { useEffect } from "react";
import { AppState, Dimensions, NativeModules, Platform } from "react-native";

const tg = new Tailglow({
  url: "https://ingest.tailglow.io",
  key: "tg_ingest_your_key",

  // Inject RN modules (the SDK doesn't import react-native directly,
  // so the package stays usable in test environments).
  appState: AppState,
  platform: Platform,
  dimensions: Dimensions.get("window"),
  nativeConstants: NativeModules.PlatformConstants,

  // Persistent queue across cold starts.
  storageAdapter: createAsyncStorageAdapter(AsyncStorage),

  // Optional context.
  context: { release: "2.1.0", environment: "production" }
});

export default function App() {
  const navigationRef = useNavigationContainerRef();
  useEffect(() => {
    tg.attachNavigation(navigationRef); // → automatic type=page_view records on the events collection
  }, []);

  return <NavigationContainer ref={navigationRef}>{/* your screens */}</NavigationContainer>;
}

After this, the SDK automatically captures:

  • JS errors: uncaught exceptions via ErrorUtils.setGlobalHandler. On isFatal=true, the SDK fires persistRemaining() (fire-and-forget) so the queued event has a chance to land in AsyncStorage before the runtime tears down. Best-effort, not guaranteed.
  • Promise rejections: HermesInternal.enablePromiseRejectionTracker (Hermes default since RN 0.70), with a fallback unhandledrejection listener for JSC. Some bundler/version combos may miss rejections; if autoConsole is on, the user-visible "Possible Unhandled Promise Rejection" string still gets captured via the console wrapper.
  • Console activity: console.error / console.warn emit records by default; log / info / debug feed the breadcrumb buffer attached to the next captured error but don't emit by themselves. Configurable via autoConsole.
  • App lifecycle: on AppState change to background or inactive, the SDK calls persistRemaining() first (fast AsyncStorage write) and then attempts a best-effort flush. Records survive even when the OS suspends the runtime mid-network because they're already on disk for the next launch. Server-side event_id dedupes if records get sent both ways.
  • Screen views: every navigation transition emits a type: "page_view" record (to the configured events collection) with from_screen, to_screen, duration_ms, nav_type, params. Time-on-screen excludes time the app was backgrounded. Each transition also leaves a navigation breadcrumb (screen names only, never params) attached to the next captured error.
  • Device: one-time device record on init (platform, OS version, screen, brand/model when available).

Reliability notes

  • Fatal JS crashes: capture is best-effort. The runtime may die before the AsyncStorage write completes. With a configured storageAdapter the chance of survival is non-zero but not 100%.
  • The final screen's duration can be lost if the app is force-killed without returning to active. The duration of the previous screen (already captured on transition) is unaffected.
  • Scope: this SDK captures JS-layer behavior. Native module crashes (iOS Objective-C / Swift, Android Java / Kotlin) are not in scope.

Expo Router

Expo Router uses React Navigation under the hood, so the same attachNavigation helper works. Get the underlying navigation container ref from useNavigationContainerRef, exported by expo-router:

// app/_layout.tsx
import { useNavigationContainerRef } from "expo-router";
import { useEffect } from "react";
import { tg } from "./tailglow"; // your Tailglow singleton

export default function Layout() {
  const navigationRef = useNavigationContainerRef();
  useEffect(() => {
    tg.attachNavigation(navigationRef);
  }, []);

  return /* your <Stack /> or <Tabs /> */;
}

page_view records use from_screen / to_screen fields populated from the Expo Router route names (which match the file system paths under app/).

Manual capture

try {
  await checkout();
} catch (err) {
  tg.captureException(err, { user_step: "checkout", cart_total: 99 });
}

tg.captureMessage("payment validator returned null", { level: "warning" });

tg.track("purchase", { amount: 99, currency: "USD" });

Tracking interactions: <TailglowPressable> and useTailglow()

React-aware components live at the @tailglow/react-native/components subpath (separate from the main package entry, which stays loadable in non-RN tooling environments). Both react and react-native must be installed at the customer's site; they are declared as peer dependencies on this package.

For ergonomic click tracking without writing tg.track() in every onPress handler, mount <TailglowProvider> once at the root and use <TailglowPressable> in place of <Pressable>:

// At app root
// In any component
import { TailglowPressable, TailglowProvider } from "@tailglow/react-native/components";
import { tg } from "./tailglow";

function App() {
  return (
    <TailglowProvider tg={tg}>
      <NavigationContainer>{/* screens */}</NavigationContainer>
    </TailglowProvider>
  );
}

function CheckoutButton() {
  return (
    <TailglowPressable
      event="checkout_clicked"
      props={{ cart_total: 247 }}
      onPress={() => router.push("/payment")}
    >
      <Text>Checkout</Text>
    </TailglowPressable>
  );
}

Behavior:

  • Fires tg.track(event, props) BEFORE calling the customer's onPress. If the customer's handler throws or navigates, the track has already fired.
  • Without an event prop, behaves as a transparent Pressable (no track call). Lets you use the same component everywhere and opt into tracking per usage.
  • Without <TailglowProvider> mounted above, also behaves as a transparent Pressable. Components remain renderable in tests / isolated screens.
  • Telemetry failures are swallowed, never block the customer's onPress.

For inline access in custom handlers, use the hook:

import { useTailglow } from "@tailglow/react-native/components";

function MyComponent() {
  const tg = useTailglow();
  return (
    <Pressable onPress={() => tg?.track("dismissed_modal", { source: "swipe" })}>
      <Text>Dismiss</Text>
    </Pressable>
  );
}

The hook returns null when no provider is mounted, so the ?. is meaningful.

Console capture defaults

autoConsole: ["error", "warn"] by default. console.error and console.warn emit records on the wire; log / info / debug still feed the breadcrumb buffer attached to the next captured error but don't emit by themselves.

// Capture everything as records (data-lake mode)
new Tailglow({ ..., autoConsole: ["log", "warn", "info", "debug", "error"] });

// Disable entirely (no wrapping, no breadcrumbs from console)
new Tailglow({ ..., autoConsole: [] });

Cold-start persistence

Pass an AsyncStorage adapter to storageAdapter so records that didn't flush before the OS killed the JS runtime get restored on next launch:

import AsyncStorage from "@react-native-async-storage/async-storage";
import { createAsyncStorageAdapter } from "@tailglow/react-native";

new Tailglow({
  ...,
  storageAdapter: createAsyncStorageAdapter(AsyncStorage)
});

The adapter is lazy. If you don't pass it, the SDK still works in-memory; you just lose any records that hadn't successfully POSTed before the app was killed.

Identity

tg.identify("usr_123"); // set the user ID
tg.unidentify(); // clear
tg.setDeviceId("dev_456"); // optional, customer-supplied stable device ID
tg.rotateSession(); // force a new session at workflow boundaries
tg.setContext({ plan: "pro" }); // sticky fields stamped on every record

Configuration

| Option | Type | Default | Description | | ------------------ | --------------------- | -------------------------------------------------- | --------------------------------------- | | url | string | required | Ingest endpoint | | key | string | required | Ingest key (tg_ingest_...) | | context | object | | Sticky fields stamped on every record | | userId | string | | Initial user ID | | deviceId | string | | Initial device ID | | storageAdapter | StorageAdapter | | Wrap with createAsyncStorageAdapter() | | flushInterval | number | 30000 | Auto-flush interval (ms) | | flushSize | number | 100 | Auto-flush at this record count | | maxBatchBytes | number | 15000000 | Max bytes per batch | | maxQueueSize | number | 10000 | Max in-memory records | | maxRecordBytes | number | 1000000 (1 MB) | Drop records larger than this | | sessionTimeout | number | 1800000 | Session inactivity timeout | | sampleRate | number | 1.0 | Sticky sampling rate | | errorBurst | object | {threshold:10,window_ms:1000,cooldown_ms:120000} | Per-fingerprint rate limit | | breadcrumbBuffer | number | 100 | Breadcrumb ring size | | redact | object | | URL token / email / field redaction | | onBeforeSend | function | | Filter records pre-queue | | onTransportError | function | | Permanent transport-failure callback | | autoErrors | boolean | true | Wire global error + rejection handlers | | autoConsole | string[] | ["error","warn"] | Levels that emit records | | autoDevice | boolean | true | Send device record on init | | autoAppState | boolean | true | Subscribe to AppState for flush-on-bg | | appState | AppState | | Required if autoAppState !== false | | platform | Platform | | Required if autoDevice !== false | | dimensions | Dimensions.get(...) | | Required if autoDevice !== false | | nativeConstants | object | | Optional; adds brand/model fields | | debug | boolean | false | Console-log SDK activity |