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

noibu-react-native

v2.1.0

Published

Noibu session replay and error monitoring SDK for React Native

Readme

noibu-react-native

Noibu session replay and error monitoring for React Native. The package is a thin JavaScript layer over the same native Noibu SDKs used by every Noibu mobile app, so replay, touches, keyboard, network and crash capture all happen natively — you install the package, call Noibu.init, and tell it which screen the user is on.

Current version: 2.1.0. See the CHANGELOG for what changed in each release.

Requirements

  • React Native 0.80 or newer, React 19.1 or newer.
  • Android: minSdkVersion 26 (Android 8.0) or higher, compileSdkVersion 36 or higher, Kotlin 2.1 or newer. React Native's template already sets the last two on 0.81+; details under Android.
  • iOS: a deployment target of iOS 14.0 or newer, which every supported React Native already sets.
  • react-native-webview 13 or newer, only if you use NoibuWebView.

Both the old and the new React Native architecture are supported.

Install

npm install noibu-react-native
cd ios && pod install   # iOS

Autolinking does the rest on both platforms. Two things to check once:

Android

Open android/build.gradle and make sure of:

  • minSdkVersion must be at least 26. React Native's template sets 23 or 24, so a default app fails to build until it is raised (the error mentions uses-sdk:minSdkVersion … cannot be smaller than version 26).
  • compileSdkVersion must be at least 36. Templates from React Native 0.81 already set it; on 0.80 raise it from 35 (the error mentions requires … to compile against version 36 or later).
  • Kotlin 2.1 or newer. Every template from React Native 0.80 pins it, so only an app that overrides kotlinVersion with an older one is affected (the error mentions compiled with an incompatible version of Kotlin).

iOS

The Noibu iOS SDK ships inside the package, so pod install is all it takes: nothing to add to your Podfile, no extra pod source and no Swift Package to add in Xcode.

  • Keep your Podfile's linkage as it is — use_frameworks! :linkage => :static (the React Native default) or no use_frameworks! at all both work. Do not switch to plain use_frameworks!; Hermes does not support it.
  • Xcode 15 or newer: set ENABLE_USER_SCRIPT_SANDBOXING to No on your app target, or the build fails with Sandbox: rsync … Operation not permitted and the app crashes at launch with Library not loaded: @rpath/coreKit.framework/coreKit. From the Podfile:
post_install do |installer|
  installer.aggregate_targets.each do |aggregate_target|
    aggregate_target.user_project.native_targets.each do |target|
      target.build_configurations.each do |config|
        config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
      end
    end
    aggregate_target.user_project.save
  end
end

Initialize

import { Noibu } from 'noibu-react-native';

Noibu.init({
  domain: 'shop.example.com', // provided by Noibu
});

Call it early, at the top of index.js or App.tsx. Capture starts natively from there. init resolves true once the SDK is running and false if it could not start (for example an empty domain); it never rejects. Optional settings, shown with their defaults:

Noibu.init({
  domain: 'shop.example.com',
  logLevel: undefined,         // 'none' | 'error' | 'warning' | 'info' | 'debug' | 'verbose'
  trackTouches: true,          // taps and scrolls
  trackKeyboard: true,         // which field was typed in — never the text
  trackNetwork: true,          // all HTTP capture
  trackErrors: true,           // JS errors and native crashes
  trackWebViews: true,         // capture inside NoibuWebView
});

Pages (trackNavigation)

How pages work in 2.0

  • Pages are reported explicitly. The SDK no longer detects screens: no route or navigation state change opens a page on its own.
  • Call Noibu.trackNavigation(screenName, attributes?, title?) for every page, including tabs, relevant modals and WebView steps (see Web steps as pages).
  • Track page groups, not individual views: screenName is the page group (Product Detail, Checkout), and attributes carry the variations (productId, step).
  • Report every page entry, including Back.
  • Repeating the same name and attributes continues the current visit; changing the attributes starts a new one.
  • Navigation helpers (useNoibuNavigation, registerNavigationContainer) don't report pages; they only manage transitions.

Page groups

A screen name is a page group, not a view. A page is everything the user experiences as one place in your app — not a single screen, route or component. Page analysis, heatmaps and Journeys aggregate by screen name, so every view of that place reports the same one:

  • every product detail, its gallery, size picker and reviews sheet → Product Detail
  • every category or listing → Product List
  • the address, payment and review steps of checkout → Checkout

| Think of it as… | Not as… | |---|---| | Product Detail (all product details) | ProductDetailScreen | | Product List (any listing) | WomenTopsCategoryScreen | | Checkout (all steps) | CheckoutAddressScreen, CheckoutPaymentScreen |

Reporting a page

Report each screen with Noibu.trackNavigation(screenName, attributes?, title?) when it gains focus — with another router, when its route state changes — keyed by the route's key, so a revisited screen is reported again:

import { Noibu, PageType } from 'noibu-react-native';

Noibu.trackNavigation('Product Detail', { pageType: PageType.Product, category: 'Clothing', productId: '123' }, 'Running Shoes');
  • screenName — the page group the user is in (Product Detail, Cart, Checkout), shared by every view of that place. Trimmed, and the page group is cut at 100 characters; a blank name reports nothing.
  • attributes — what varies inside the page (category, productId, step…). The first 20 keys of 1–50 characters; values are strings (cut at 200 characters), booleans, finite numbers or null. Anything else is dropped, never thrown.
  • title (optional) — the page's human-readable title, what Noibu shows for it (the product's name on a product page, say). Without one, the screen name is the title. Trimmed and cut at 1024 characters. It does not identify the visit: a repeated call keeps the visit's first title.
  • pageType (optional attribute) — places the page in a standard ecommerce category: PageType.Home, Collection, Search, Product, Cart, Checkout, Login (matched ignoring case). Journeys groups and colours the page under that category; without it, the page is grouped by its screen name. Only the exact key pageType is read: pagetype or page_type is an ordinary attribute.

A trackNavigation made before init resolves is held, and the last one is sent once it does.

Repeated calls are safe. Focus effects and listeners can fire again while the user stays on the same page (a modal closed over it, a re-run effect). Reporting the page that is already current, with unchanged attributes, continues the existing visit; only a different name or different attributes starts a new one.

React Navigation

Install useNoibuNavigation(navigationRef) and report each screen from useFocusEffect. The hook keeps replay transitions clean and lands an event tracked from the new screen's mount effect on that screen's page (see Tracking events); it does not report pages.

import { createNavigationContainerRef, NavigationContainer, useFocusEffect } from '@react-navigation/native';
import { Noibu, PageType, useNoibuNavigation } from 'noibu-react-native';

const navigationRef = createNavigationContainerRef();

function App() {
  useNoibuNavigation(navigationRef);
  return <NavigationContainer ref={navigationRef}>…</NavigationContainer>;
}

function ProductDetailScreen({ route }) {
  useFocusEffect(
    React.useCallback(() => {
      Noibu.trackNavigation('Product Detail', { pageType: PageType.Product, category: 'Clothing', productId: route.params.id });
    }, [route.key]),
  );
  …
}

Outside a component, registerNavigationContainer(ref) does the same as the hook and returns an unsubscribe function.

Naming your pages

| Screen | screenName | pageType | Example attributes | |---|---|---|---| | Home | Home | Home | — | | Category listing | Product List | Collection | category, subcategory | | Search results | Search | Search | query | | Product detail | Product Detail | Product | category, productType, productId | | Cart | Cart | Cart | — | | Checkout | Checkout | Checkout | step | | Order confirmation | Order Confirmed | — | orderId | | Sign in | Login | Login | — |

  • Template, not content: every product detail is Product Detail; ids and user data never go in the name.
  • Hierarchy and ids go in attributes (Product Detail + category, not Product Detail - Clothing - Tops).
  • Attributes identify the visit: the same name with different attributes starts a new visit. null is a value: { size: null } and no size key are different visits. Every view of a page reports the same attributes; transient state (selected size, scroll position) stays out.
  • Small and stable: well under 100 names, unchanged between releases. Reserved names (None, Page Check, External traffic, Exit, Other pages, No Page Group, All) are sent without a group.

What happens without a call

The SDK opens a page on its own when there is no call to take it:

  • On a fresh start (no previous screen), events, errors and replay land on an unnamed view/<uuid> page with no group until the first call. If the app reports its first screen within 2 seconds, or before any replay frame is captured, that screen takes the page over; otherwise the placeholder is sent as a visit of its own.
  • On a return from background, a relaunch or a new session, the next page copies the previous screen's name, group, attributes and title.
  • An app that never calls trackNavigation still records sessions, made of view/<uuid> pages.

Migrating from 1.x

| 1.x | 2.0 | |---|---| | Noibu.trackNavigation() (no name) | Noibu.trackNavigation(screenName, attributes?) | | autoTrackNavigation: true in init | Removed — call trackNavigation from each screen | | useNoibuNavigation(ref) / registerNavigationContainer(ref, onNavigate) reporting a page per route | Same install line, plus trackNavigation on focus; onNavigate is removed |

Apps that relied on automatic tracking must now call trackNavigation on every page; the useNoibuNavigation install line now only keeps transitions clean.

Bottom tab bars in page analysis

Page analysis pins the screen's bottom bar to the bottom of the snapshot while the rest of the page expands. @react-navigation/bottom-tabs bars are recognised as is. A hand-rolled bar declares itself with accessibilityRole on the bar View — 'tablist' on Android (read from the view tag) and 'tabbar' on iOS (the only role React Native turns into the .tabBar trait), so Platform.OS === 'ios' ? 'tabbar' : 'tablist' — and accessibilityRole="tab" on each item.

Errors and attributes

Noibu.addError(new Error('Payment declined'));
Noibu.addError('Payment declined', stackString);
Noibu.addCustomAttribute('customerId', '42');

You do not need to wait for init: anything reported before it lands is held and applied once it does. addCustomAttribute returns SUCCESS or a reason it was not stored (TOO_MANY_IDS_ADDED above 10 per session, NAME_TOO_LONG / VALUE_TOO_LONG above 50 characters, ID_NAME_ALREADY_ADDED, NOT_INITIALIZED, …). Uncaught JavaScript errors, unhandled promise rejections and native crashes are captured automatically; wrap a subtree with ErrorBoundary to also catch React render errors:

import { ErrorBoundary } from 'noibu-react-native';
<ErrorBoundary fallback={<Oops />}>…</ErrorBoundary>

fallback is an element or a function receiving { error, componentStack, eventId, resetError }; the boundary also accepts onError, onMount, onReset and onUnmount.

Tracking events

Noibu.track(name, payload) works like window.NOIBUJS.track on web: a standard ecommerce event with a payload following the ecommerce schema, or any custom event with a JSON object.

Noibu.track('product_added_to_cart', {
  cartLine: { quantity: 1, merchandise: { id: 'sku-42', title: 'Running Shoes' } },
});
const { success, errors } = Noibu.track('promo_banner_dismissed', { campaign: 'summer' });

It returns { success, errors } and never throws. Names are capped at 500 characters and payloads at 10,000 characters serialized; standard payloads are checked against the ecommerce schema, and a type mismatch is reported in errors instead of being sent. Events tracked before init lands are held (up to 100), and an event tracked as a screen mounts is attributed to that screen.

| Event name | Payload key | Payload | |---|---|---| | product_viewed | productVariant | { id, sku, title, price: Money, product: { id, title, type, url, vendor } } | | product_added_to_cart, product_removed_from_cart | cartLine | { quantity, merchandise: productVariant, cost: { totalAmount: Money } } | | cart_viewed | cart | { id, totalQuantity, lines: [cartLine], cost: { totalAmount: Money } } | | collection_viewed | collection | { id, title, productVariants: [productVariant] } | | search_submitted | searchResult | { query, productVariants: [productVariant] } | | checkout_started, checkout_address_info_submitted, checkout_contact_info_submitted, checkout_shipping_info_submitted, payment_info_submitted, checkout_completed | checkout | { token, currencyCode, subtotalPrice, totalTax, totalPrice, lineItems: [{ id, title, quantity, variant: productVariant, finalLinePrice }], shippingLine: { price }, discountApplications, order: { id, customer: { id, isFirstOrder } }, transactions } |

Money is { amount, currencyCode }. Every field is optional. Any other event name is a custom event.

Network capture

fetch, XHR, remote <Image> loads and native requests are captured on both platforms without any wiring, on bare React Native and on Expo alike. Bodies and header values are captured up to 64 KB and scrubbed of personal data (passwords, emails, card numbers, phone numbers, …) before they are stored. A GraphQL response that returns 2xx with an errors array is reported as an error. trackNetwork: false turns all of it off; there is no per-URL ignore list.

If your app installs its own HTTP client factory, add Noibu inside it (both calls are safe to repeat):

// Android
OkHttpClientProvider.setOkHttpClientFactory {
  OkHttpClientProvider.createClientBuilder(context)
    .installNetworkInstrumentation() // com.noibu.mobile.sessionreplay.api
    .build()
}
// iOS
RCTSetCustomNSURLSessionConfigurationProvider(^NSURLSessionConfiguration *{
  NSURLSessionConfiguration *configuration = NSURLSessionConfiguration.defaultSessionConfiguration;
  [[NoibuHTTPInterceptor shared] installNetworkInstrumentationOn:configuration]; // @import NoibuSessionReplay
  return configuration;
});

WebView capture

Use NoibuWebView in place of react-native-webview's WebView where you want the web content to be part of the session — its replay, clicks, JavaScript errors and requests join the timeline:

import { NoibuWebView } from 'noibu-react-native';

<NoibuWebView source={{ uri: 'https://shop.example.com/checkout' }} />

It takes the same props as WebView and fills its parent by default. Webviews rendered with the plain WebView are not captured, so a sensitive one (payment 3DS, SSO) stays out by not wrapping it. Inputs inside a tracked webview are masked in replay. trackWebViews: false disables webview capture everywhere.

The webview opens no page: what it captures lands on the current page, including after a navigation inside the webview.

Web steps as pages

To see each step of a web flow as its own page, report every step with trackNavigation under the hosting screen's name, with the step's path as an attribute: the hosting screen reports the document the webview loads first, and onRouteChange reports each later one.

function CheckoutScreen() {
  const webPath = React.useRef('/checkout'); // path?query of the document the webview loads first
  useFocusEffect(
    React.useCallback(() => {
      Noibu.trackNavigation('Checkout', { path: webPath.current });
    }, []),
  );
  return (
    <NoibuWebView
      source={{ uri: 'https://shop.example.com/checkout' }}
      onRouteChange={(path) => {
        webPath.current = path;
        Noibu.trackNavigation('Checkout', { path });
      }}
    />
  );
}

The hosting screen's first path is the key onRouteChange would use for that URL: its path and query, with the fragment dropped and an empty path as / (https://shop.example.com/checkout?step=1#top → /checkout?step=1). For an inline document loaded with a base URL, use that base URL (https://example.com/ → /).

The SDK calls onRouteChange with the new route's path?query (the fragment is ignored) each time the page moves to a different one, through the browser history or a new document. It skips the document the webview loads first, which the hosting screen reports, and opens no page itself.

Returning to the first document later comes through onRouteChange with that same path.

When the hosting screen reports itself again (on focus), it reports the step it is on — the last path, starting from the first document's — so the current visit continues.

Lifecycle

Noibu.isInitialized is true between a successful init and shutdown. shutdown() stops capture, flushes what was recorded and lets you call init again later in the same process (after a logout, for example). A session that stays in the background for 15 minutes ends there; the next return starts a new one.

Known limitations

  • Privacy mode is not selectable. Card, SSN, email and phone numbers in rendered text are always scrubbed; text typed into a <TextInput> is never captured, only which field was used.
  • Animated content replays as a still. A GIF or animated WebP on Android shows its first frame, an <ActivityIndicator> its first frame, and a continuously animating SVG, <Switch>, progress bar or slider freezes after its first 30 changes.
  • iOS truncated text (numberOfLines={1}) replays in full and may overrun what sits beside it.
  • iOS borders under the new architecture are not captured: an outlined card, list separator or input underline replays without its border. Android and the old architecture capture them.
  • Android gradients on React Native 0.80–0.82: a backgroundImage linear gradient replays as an empty box; from 0.83 it is captured. Solid backgrounds, borders and corner radii capture on every supported version.