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

@avsbhq/react

v1.0.0

Published

React SDK for the [A vs B](https://app.avsb.cloud) platform.

Downloads

262

Readme

@avsbhq/react

React SDK for the A vs B platform.

Wrap your component tree with <AvsbProvider> and read feature flags anywhere with hooks. Built on @avsbhq/browser; components re-render automatically when flag values change. Every hook is backed by useSyncExternalStore for tear-free concurrent mode updates.


1. Install

npm install @avsbhq/react

React 18 or later is required as a peer dependency. @avsbhq/browser ships as a runtime dependency — you do not need to install it separately.


2. Quickstart

// app.tsx — or your root layout
import { AvsbProvider } from '@avsbhq/react'

export function App({ children }: { children: React.ReactNode }) {
  return (
    <AvsbProvider
      sdkKey={process.env.NEXT_PUBLIC_AVSB_SDK_KEY!}
      context={{ kind: 'user', key: userId, plan: userPlan }}
    >
      {children}
    </AvsbProvider>
  )
}
// CheckoutButton.tsx
import { useBoolFlag, useTrack } from '@avsbhq/react'

export function CheckoutButton() {
  const newCheckout = useBoolFlag('new-checkout-flow', false)
  const track = useTrack()

  return (
    <button onClick={() => track('checkout_clicked', { properties: { location: 'header' } })}>
      {newCheckout.isEnabled() ? 'Start checkout (new)' : 'Buy now'}
    </button>
  )
}

3. SDK keys

Get a client SDK key from your A vs B project's Environments page:

NEXT_PUBLIC_AVSB_SDK_KEY=sdk-client-...
VITE_AVSB_SDK_KEY=sdk-client-...

Client SDK keys are safe in browser bundles. Never use a server SDK key (sdk-server-...) with @avsbhq/react.

See @avsbhq/browser documentation for key rotation.


4. Identity

Provider-level context

Pass a context object to <AvsbProvider> at bootstrap. It is the evaluation context used for all flag reads until you call identify:

<AvsbProvider
  sdkKey={sdkKey}
  context={{ kind: 'user', key: userId, email: userEmail, plan: 'pro' }}
>

When context changes between renders, <AvsbProvider> calls client.identify() automatically and re-evaluates all flags.

useIdentify

Replace the entire context at runtime (e.g. after sign-in):

import { useIdentify } from '@avsbhq/react'

const identify = useIdentify()

function onSignIn(user: User) {
  identify({ kind: 'user', key: user.id, email: user.email, plan: user.plan })
}

The returned function is referentially stable across renders.

useAlias

Link an anonymous visitor to an identified user. Call this once at sign-in:

import { useAlias } from '@avsbhq/react'

const alias = useAlias()

async function onSignIn(anonKey: string, user: User) {
  await alias(
    { kind: 'user', key: anonKey },
    { kind: 'user', key: user.id }
  )
  identify({ kind: 'user', key: user.id, plan: user.plan })
}

useReset

Return to an anonymous context on logout:

import { useReset } from '@avsbhq/react'

const reset = useReset()
function onSignOut() { reset() }

5. Multi-context

identify({
  kind: 'multi',
  user: { kind: 'user', key: 'u_123', plan: 'pro' },
  organization: { kind: 'organization', key: 'org_456', tier: 'enterprise' },
})

After calling identify with a multi-context, all hooks evaluate against every context kind simultaneously.


6. Reading flags

Typed hooks

Use the typed hooks to get a runtime type-safety check and the full Flag<T> object:

import { useBoolFlag, useStringFlag, useNumberFlag, useJsonFlag } from '@avsbhq/react'

const darkMode  = useBoolFlag('dark-mode', false)
const theme     = useStringFlag('theme', 'light')
const maxItems  = useNumberFlag('max-results', 25)
const config    = useJsonFlag<{ timeout: number }>('api-config', { timeout: 5000 })

All return Flag<T>. A defaultValue is always required.

useFlag<T> — generic

import { useFlag } from '@avsbhq/react'

const pricing = useFlag<{ tiers: number }>('pricing-config', { tiers: 3 })

useFlagValue<T> — raw value shortcut

Returns T directly when you only need the value and not the metadata:

import { useFlagValue } from '@avsbhq/react'

const showBanner = useFlagValue('promo-banner', false)
// showBanner is boolean

The Flag<T> object

flag.value          // T — the variation value
flag.isEnabled()    // true if source === 'rule' && value is truthy
flag.variationKey   // 'on' | 'off' | 'control' | null
flag.exists()       // false when source === 'not_found'
flag.source         // 'rule' | 'sticky' | 'holdout' | 'default' | ...
flag.ruleId         // which rule matched
flag.reasons        // string[] — evaluation path

Variant rendering

Use variationKey when you need to branch on a specific variation identity rather than a boolean gate:

import { useStringFlag } from '@avsbhq/react'

function PricingPage() {
  const pricingExp = useStringFlag('pricing-experiment', 'control')

  if (pricingExp.variationKey === 'three-tier') return <ThreeTierPricing />
  if (pricingExp.variationKey === 'usage-based') return <UsageBasedPricing />
  return <DefaultPricing />
}

useAllFlags

import { useAllFlags } from '@avsbhq/react'

const flags = useAllFlags()
// Record<string, Flag> — re-renders on any flagChange event

Prefer individual typed hooks in components. useAllFlags is suited for debug panels or bulk snapshot use.

Suspense

import { useFlagSuspense } from '@avsbhq/react'

function CheckoutFeature() {
  const flag = useFlagSuspense('checkout-v2', false)
  return flag.isEnabled() ? <CheckoutV2 /> : <Checkout />
}

// In parent:
<Suspense fallback={<Skeleton />}>
  <CheckoutFeature />
</Suspense>

useFlagSuspense throws the client.onReady() Promise while the SDK is loading, then resolves with the flag value.

Status hook

import { useAvsbStatus } from '@avsbhq/react'

const { status, error } = useAvsbStatus()
// status: 'loading' | 'ready' | 'error'

7. Tracking events

import { useTrack } from '@avsbhq/react'

const track = useTrack()

function handlePurchase() {
  track('purchase_completed', {
    value: 199.0,
    properties: { plan: 'annual' },
  })
}

useTrack returns a stable function that does not change reference across renders.


8. Error handling

Provider-level error state

Use useFlagReady to gate rendering until the SDK is ready:

import { useFlagReady } from '@avsbhq/react'

function App() {
  const ready = useFlagReady()
  if (!ready) return <Skeleton />
  return <MainContent />
}

Custom logger

import { createLogger, consoleTransport } from '@avsbhq/browser'

<AvsbProvider
  sdkKey={sdkKey}
  context={ctx}
  logger={createLogger({
    level: 'warn',
    transports: [consoleTransport({ level: 'warn' })],
  })}
>

onError callback

<AvsbProvider
  sdkKey={sdkKey}
  context={ctx}
  onError={(err, source) => {
    Sentry.captureException(err, { tags: { sdkSource: source } })
  }}
>

Direct client access

import { useAvsbClient } from '@avsbhq/react'

const client = useAvsbClient()

React.useEffect(() => {
  if (!client) return
  return client.on('error', (err) => {
    myLogger.error('SDK error', { error: err.message })
  })
}, [client])

9. SSR / hydration

Pass a bootstrap datafile to skip the initial network round-trip on the client and ensure the server render matches the hydration output:

// Server: fetch the datafile and pass it as a prop or window variable
<AvsbProvider
  sdkKey={sdkKey}
  context={ctx}
  bootstrap={bootstrapDatafileFromServer}
>
// Server-side: generate the bootstrap blob
const serverClient = new AvsbClient({ sdkKey: serverKey, context: ctx })
await serverClient.onReady()
const bootstrap = await serverClient.utils.serializeBootstrap(ctx)
// serialize to JSON and embed in the HTML response

For Next.js App Router, use @avsbhq/next instead — it provides AvsbHydrator and AvsbServerProvider which handle the server-to-client bootstrap automatically.

Flag hooks return a not_found sentinel during the hydration window so the SSR output matches the initial client render and avoids React hydration mismatches.

Pre-built client injection (Mode B)

When you own the SDK client lifecycle:

const preBuiltClient = new AvsbClient({ sdkKey, context })
await preBuiltClient.onReady()

<AvsbProvider client={preBuiltClient}>
  {children}
</AvsbProvider>

When client is provided, <AvsbProvider> does not call client.close() on unmount — you are responsible for teardown.


10. Graceful shutdown

When using Mode A (sdkKey prop), <AvsbProvider> automatically closes the client on unmount and flushes queued events.

When using Mode B (pre-built client):

useEffect(() => {
  return () => { void client.close() }  // flush + teardown
}, [client])

For tab close in any mode:

useEffect(() => {
  const handler = () => { void client?.flush() }
  window.addEventListener('beforeunload', handler)
  return () => window.removeEventListener('beforeunload', handler)
}, [client])

11. Testing

import { render, screen } from '@testing-library/react'
import { AvsbTestProvider } from '@avsbhq/test'
import { CheckoutButton } from './CheckoutButton'

test('shows new checkout when flag is enabled', () => {
  render(
    <AvsbTestProvider mockFlags={{ 'new-checkout-flow': true }}>
      <CheckoutButton />
    </AvsbTestProvider>
  )
  expect(screen.getByRole('button')).toHaveTextContent('Start checkout (new)')
})

test('shows default when flag is off', () => {
  render(
    <AvsbTestProvider mockFlags={{ 'new-checkout-flow': false }}>
      <CheckoutButton />
    </AvsbTestProvider>
  )
  expect(screen.getByRole('button')).toHaveTextContent('Buy now')
})

For fluent per-user control:

import { TestData, createMockClient } from '@avsbhq/test'

const td = TestData.flag('checkout-v2')
  .booleanFlag()
  .variationForUser('u_paying', true)
  .fallthroughVariation(false)

const client = createMockClient({ flags: [td.build()] })

For non-rendering subscriptions:

import { useFlagSubscription } from '@avsbhq/react'

// In your component test — emits callbacks without triggering re-render
useFlagSubscription('checkout-v2', (flag) => {
  myAnalytics.record(flag.variationKey)
})

12. Migration

From LaunchDarkly React

| LaunchDarkly React | @avsbhq/react | |---|---| | <LDProvider clientSideID="..."> | <AvsbProvider sdkKey="..."> | | useLDClient() | useAvsbClient() | | useFlags() | useAllFlags() | | useLDFlag('key', default) | useFlag('key', default).value | | ldClient.identify(context) | useIdentify()(context) | | ldClient.track('event') | useTrack()('event') | | withLDProvider(options)(App) | <AvsbProvider ...> — no HOC needed |

Key differences:

  • useFlag returns a Flag<T> object, not a raw T. Access .value for the primitive or use useFlagValue for a convenience shortcut.
  • All hooks require an explicit defaultValue.
  • Multi-context is native — pass { kind: 'multi', user: ..., organization: ... } to <AvsbProvider context={...}>.
  • useFlagDetails is gone — the unified Flag<T> from useFlag carries the same metadata (variationKey, source, reasons, ruleId).

From Statsig React

| Statsig React | @avsbhq/react | |---|---| | <StatsigProvider sdkKey="..."> | <AvsbProvider sdkKey="..."> | | useGate('gate') | useBoolFlag('gate', false).isEnabled() | | useExperiment('exp').get('param', default) | useFlag('exp', default).value | | useStatsigClient() | useAvsbClient() | | logEvent('event', value, metadata) | useTrack()('event', { value, properties: metadata }) |