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

@test3-dev/react

v0.1.0

Published

First-class JSX experiments for React, SSR, and React Native with Test3

Readme

@test3-dev/react

React bindings for Test3 experiments. React 18 and 19 are peer dependencies; the provider owns its own TanStack Query cache. The browser, native, and server entry points keep platform-specific code separate.

Install

npm install @test3-dev/react
# or
bun add @test3-dev/react

The core SDK is installed automatically. React Native integrations also need @react-native-async-storage/async-storage and @react-native-community/netinfo. For Expo, use npx expo install expo-splash-screen @react-native-async-storage/async-storage @react-native-community/netinfo to select compatible versions.

Browser

import {
  ExperimentProvider,
  Experiment,
  Variant,
  useExperiments,
} from "@test3-dev/react"
import { browserBootstrap } from "@test3-dev/react/browser"

const manifest = {
  exp_signup: {
    variantIds: ["var_control", "var_challenger"],
    templates: [],
  },
}
const { identity, initialSnapshot } = await browserBootstrap()

function Signup() {
  const { track } = useExperiments()
  return (
    <Experiment id="exp_signup">
      <Variant id="var_control">
        <OriginalHero
          onSignup={async () => {
            const signup = await createSignup()
            await track("signup_completed", { eventId: signup.id })
          }}
        />
      </Variant>
      <Variant id="var_challenger">
        <NewHero
          onSignup={async () => {
            const signup = await createSignup()
            await track("signup_completed", { eventId: signup.id })
          }}
        />
      </Variant>
    </Experiment>
  )
}

export function App() {
  return (
    <ExperimentProvider
      apiUrl="http://localhost:3001"
      publicKey="pk_…"
      identity={identity}
      manifest={manifest}
      initialSnapshot={initialSnapshot}
    >
      <Signup />
    </ExperimentProvider>
  )
}

Before the application bundle, place the API bootstrap in the document head:

<head>
  <script
    src="https://api.example.com/sdk/bootstrap.js"
    data-api-url="https://api.example.com"
    data-public-key="pk_…"
    data-manifest='{"exp_signup":{"variantIds":["var_control","var_challenger"],"templates":[]}}'
  ></script>
</head>

This must remain a classic script in head: do not add async, defer, or type="module". The browser pauses HTML parsing for the script download and its synchronous assignment request. The script stores the resulting snapshot before parsing resumes; browserBootstrap() retrieves that same identity and snapshot. If bootstrap fails, the application does not mount an assumed arm.

The experiment and variant IDs come from the dashboard, but the components and everything they display remain in application JSX. Keep identity and the manifest outside rendering. Both arms must be listed in the manifest. Ambiguous or missing bindings render nothing and emit a diagnostic; hidden branches never mount.

The conversion event must match the experiment goal. Supply a stable event ID from the completed business action, not a new ID on each delivery retry. Relevant exposure receipts are attached by the SDK; callers cannot choose the attributed variant. Exposure means a committed mount, not viewport visibility. Reading useExperiment(id) does not track exposure; call its trackExposure() for a non-component decision that was actually used.

useExperiments() exposes ready, error, stale, lastSuccessfulRefresh, queuedEvents, diagnostics, track, refresh, flush, and advanceJourney. The provider does not mount its application subtree without a definitive snapshot, so it can never commit one arm and replace it during initial evaluation. Active, online clients refresh every 30 seconds and on focus/foreground/reconnect. Manual force, finish, pause, and resume decisions apply on refresh. Automatic changes to mounted branches wait for advanceJourney() or a fresh component mount; call that method at a safe navigation/form boundary. A forced change can remount UI and reset component state.

Snapshots are scoped to the environment key, API origin, subject, and release manifest, usable for up to 24 hours during an outage. A bounded persistent queue retains stable event IDs and timestamps for up to seven days and retries individual unacknowledged events. Offline devices cannot receive an immediate remote override. Event and snapshot storage errors fall back to memory and appear in diagnostics.

SSR

Use evaluateExperiments and serializeSnapshot from @test3-dev/react/server. Read/create an opaque subject cookie per request and finish evaluation before rendering HTML. Pass the exact server subject and snapshot as initialSnapshot to the client provider, and safely embed the serialized data in an application/json script for hydration. If evaluation fails, return an application-level error response instead of rendering an assumed arm. Use Cache-Control: private, no-store for personalized HTML. No exposure is sent on the server; hydration commits it. Server Components can call the helper and pass results to a client provider boundary.

React Native

import AsyncStorage from "@react-native-async-storage/async-storage"
import NetInfo from "@react-native-community/netinfo"
import { AppState } from "react-native"
import * as SplashScreen from "expo-splash-screen"
import {
  nativeBootstrap,
  nativeIdentity,
  nativeLifecycle,
} from "@test3-dev/react/native"
const identity = nativeIdentity({
  storage: AsyncStorage,
  lifecycle: nativeLifecycle({ AppState, NetInfo }),
})
await SplashScreen.preventAutoHideAsync()
const { initialSnapshot } = await nativeBootstrap({
  apiUrl,
  publicKey,
  identity,
  manifest,
})

Pass initialSnapshot to ExperimentProvider, render it, and hide the native splash only after the root layout commits. Use native UI in Variant render props and pass active={isFocused} to each screen's Experiment. Foregrounding refreshes delivery, but automatic replacement waits for a safe journey boundary. Map approved theme tokens to native style properties; CSS variables do not style native views. Default anonymous identity remains device-specific. For cross-device identity, supply the same opaque subjectId to the identity adapter before mounting the provider. Do not send email addresses or merge anonymous histories automatically.

See the monorepo examples for full browser, SSR, and Expo integrations. Installation packages contain their public wire types and have no private workspace runtime dependency.