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

@betterreviews/react-native

v1.0.0

Published

React Native renderer for BetterReviews mobile PDP content. Consumes the betterreviews_reactiv.* Shopify metafields and renders product content blocks (features, reviews_summary) themed to the merchant.

Readme

@betterreviews/react-native

Native React Native renderer for BetterReviews mobile PDP content. Consumes the betterreviews_reactiv.* Shopify metafield namespace and renders product content blocks themed to the merchant.

Authorized Partner

This package is licensed for use by Reactiv under a written integration agreement with BetterReviews. See LICENSE.

Installation

yarn add @betterreviews/react-native \
  react-native-webview react-native-svg react-native-gesture-handler

Peer dependencies your host app must provide: react ≥18, react-native ≥0.74, react-native-webview ≥13, react-native-svg ≥15, and react-native-gesture-handler ≥2.16. valibot is bundled as a direct dependency — you don't install it.

react-native-gesture-handler setup (required by ReviewWidget's media viewer): import it as the very first line of your app entry (index.js/index.ts) and wrap your app root in GestureHandlerRootView:

import 'react-native-gesture-handler'; // must be first
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function Root() {
  return <GestureHandlerRootView style={{ flex: 1 }}>{/* app */}</GestureHandlerRootView>;
}

npm users: React Native 0.74 ships a mismatched @types/react peer range, so a plain npm install may fail with ERESOLVE. This is an upstream React Native quirk, not specific to this package — install with npm install --legacy-peer-deps, or use Yarn (which is more lenient). Yarn is recommended for React Native projects.

Quick start

import {
  BetterReviewsProvider,
  ProductContentBlock,
  type Theme,
  type Config,
  type ProductContentBlockSchema,
} from '@betterreviews/react-native';

function App() {
  // Partner host app fetches the three metafield bodies from Shopify
  // (storefront API or partner-backend proxy) for the active product.
  const theme: Theme | null = useFetchedTheme(productId);
  const config: Config | null = useFetchedConfig(productId);
  const block: ProductContentBlockSchema | null = useFetchedBlock(productId);

  return (
    <BetterReviewsProvider
      theme={theme}
      config={config}
      onTelemetryEvent={(event) => {
        // Forward to partner's own observability stack.
        partnerAnalytics.log(event);
      }}
    >
      <ProductContentBlock block={block} />
    </BetterReviewsProvider>
  );
}

Render gates

<ProductContentBlock> renders nothing (returns null) when any of these are true:

  • config.product_content_block_enabled === false — merchant explicitly disabled this product
  • config.min_sdk_version declared and current SDK below floor — emits betterreviews.fetch.failure telemetry
  • block is null / undefined
  • The block envelope fails top-level schema validation — emits betterreviews.schema.violation telemetry

Per-section validation uses tolerant-reader semantics: an individual malformed section is dropped (with telemetry) while the rest render.

Telemetry events

| Event | When | |---|---| | betterreviews.fetch.failure | Mount blocked by min_sdk_version floor (error_code: "sdk_below_floor") | | betterreviews.schema.violation | Envelope or per-section validation failed |

Future versions will emit betterreviews.fetch.success, betterreviews.signature.invalid, and betterreviews.webview.error (the last lands with the WebView host in Card C.12b).

Theming

The widgets are neutral by default — a black CTA on zinc grayscale. No brand color appears unless the host opts in by passing a theme to <BetterReviewsProvider> (background_color, text_color, accent_color, corner_style, font_family). The only non-grayscale defaults are the gold stars/bars and the green "Verified Buyer" badge (universal review conventions, fixed in v1).

StarRating (aggregate badge)

<StarRating> is the compact rating badge (stars + score + review count) for near the product title — the RN equivalent of the storefront br-star-rating block. The host supplies the aggregate (average + total, typically from the betterreviews.summary metafield it already fetches); the badge does not call the API.

import { StarRating } from '@betterreviews/react-native';

<StarRating average={4.5} total={128} onPress={scrollToReviews} />

Star color comes from <BetterReviewsProvider> theme (gold fallback outside a provider); onPress makes it a button (e.g. scroll to the ReviewWidget); it renders nothing when total is 0 (hideWhenEmpty, default on).

ReviewWidget (review browsing + voting)

<ReviewWidget> renders the full review-browsing surface — paginated list, rating/photo/search filters, sort, read-more, a full-screen media viewer, and helpful/unhelpful voting.

The package owns the UI + pagination/sort/filter state. Your host app owns transport and auth via an injected Fetcher: it prepends the API base URL, injects the widget token, and returns parsed JSON (throwing on a non-2xx status). No token ever lives inside this package or your app binary's SDK code — keep it in your host's secure config / backend proxy.

import {
  ReviewWidget,
  createBetterReviewsClient,
  type Fetcher,
} from '@betterreviews/react-native';

const fetcher: Fetcher = async ({ path, query, method = 'GET', body, signal }) => {
  const params = new URLSearchParams();
  for (const [k, v] of Object.entries(query ?? {})) if (v !== undefined) params.set(k, String(v));
  params.set('token', getWidgetTokenFromSecureConfig()); // host-injected; never hardcode
  const res = await fetch(`${API_BASE}${path}?${params}`, {
    method, signal,
    headers: body ? { 'Content-Type': 'application/json' } : undefined,
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) throw new Error(`widget request failed: HTTP ${res.status}`); // never log the URL — the token is in it
  return res.json();
};

const client = createBetterReviewsClient({ fetcher, storeId, productId });

// Theme + telemetry come from <BetterReviewsProvider> context (NOT props).
<BetterReviewsProvider theme={theme} onTelemetryEvent={partnerAnalytics.log}>
  <ReviewWidget client={client} onWriteReview={openReviewChat} />
</BetterReviewsProvider>

Notes:

  • ReviewWidget renders inline — it owns no scroll container, so drop it into your product-page ScrollView as one section (e.g. below the product info and ProductContentBlock) and it scrolls with the page. Paging is a "Load more" button (no virtualization). The sort drawer and full-screen media viewer are Modal overlays, so they work regardless.
  • onWriteReview is host-owned (open your chat WebView / nav). The CTA hides if omitted. Never pass server-controlled strings to Linking.openURL.
  • Voting persists in-memory by default; pass voteStateStore (e.g. AsyncStorage-backed) to persist "already voted" across launches. Store only review id → direction; never review content or the token.
  • The widget requires the GestureHandlerRootView root wrap above.

Security obligations on the host

See SECURITY.md § "What you must do (the host)" for the contract every embedding host app must honor — credential storage, GDPR cascade, Shopify Level 2 data scope, cache TTL ceiling, Info.plist permissions, logging discipline.

Versioning

This package follows semver. The betterreviews_reactiv.* metafield schema (generated JSON Schemas at elixir/priv/reactiv_schemas/v1/) follows additive-only compatibility with a 90-day deprecation window — see schemas/betterreviews-reactiv/COMPATIBILITY.md at the BetterReviews repo root.

WebView surface

For the customer-facing chat flow (/review/chat), use WebViewHost:

import { WebViewHost } from '@betterreviews/react-native';

<WebViewHost
  url={`https://api.betterreviews.app/review/chat?store_id=${storeId}&product_id=${productId}&token=${token}`}
  onMessage={(event) => console.log('message from chat', event.nativeEvent.data)}
  onError={(event) => console.warn('webview error', event.nativeEvent)}
  onClose={() => dismissChat()} // fires when the page posts {type:'close'} (e.g. "Back to store")
/>

The component intentionally exposes only { url, onMessage, onError }. All underlying WebView props (cookie scope, content-inset behavior, keyboard handling, media playback) are locked to the baseline validated by the Tier 2 WebView test (docs/proposals/reactiv-webview-tier2-test-2026-05-18.md). Future recovery patches stay surgical.

react-native-webview ≥13.0.0 is a peer dep. Add it to your host app:

yarn add react-native-webview

Bridge config

config.bridge declares the merchant's preference for native bridge surfaces:

  • "off" (default): everything renders in-WebView.
  • "auto": native if available, fall back to in-WebView.
  • "required": native; if not available, render nothing.

v1 of this package supports "off" only. "auto" and "required" resolve to "off" behavior and emit a betterreviews.fetch.failure telemetry event with error_code: "bridge_not_implemented" so partner observability can surface the unhonored intent. Native bridge implementations land post-soft-launch (Card C.14).

Not yet shipped

  • Native (non-WebView) bridge surfaces for the chat flow (Card C.14 — post-soft-launch)
  • Per-surface theming of the widget's fixed neutrals (star/verified/muted/border/scrim) — a later additive theme schema bump
  • HMAC signature verification (forward-compat — added when a bidirectional channel emerges)

License

Proprietary. See LICENSE. For licensing inquiries: [email protected].