SDK

React

A hooks and provider wrapper around the JavaScript/TypeScript SDK, built for React apps. Ships as ESM and CJS with full TypeScript types, and is safe to import from Next.js Server Components (marked 'use client').

View source on GitHub — shipsilently/react
This runs in the browser, so mind the key. ShipSilently has one kind of API key today and it is a server key, capable of reading every flag configuration in its environment. Shipping it to a browser bundle exposes your targeting rules. Prefer evaluating server-side and passing values down; see Browser usage for the recommended pattern.

Install

npm install @shipsilently/react
bun add @shipsilently/react
yarn add @shipsilently/react
pnpm add @shipsilently/react

Wrap your app in the provider

ShipSilentlyProvider creates one client for the component tree, evaluates every flag for the given context, and subscribes to real-time updates, falling back to polling.

App.tsx
// Mount the provider once, above anything that reads a flag. It opens a single
// stream for the given context and re-renders consumers when flags change.
//
// The provider compares the context's *contents*, not its identity — you do not
// need to memoize the object you pass.
import { ShipSilentlyProvider } from '@shipsilently/react';

export function App({ user }: { user: { id: string; plan: string } }) {
  return (
    <ShipSilentlyProvider
      config={{
        apiKey: process.env.NEXT_PUBLIC_SHIPSILENTLY_KEY!,
      }}
      context={{ userId: user.id, plan: user.plan }}
    >
      <Checkout />
    </ShipSilentlyProvider>
  );
}

function Checkout() {
  return <div>checkout</div>;
}
You don't need to memoize context. The provider re-subscribes when the object's contents change, not its identity, so an inline object literal is fine and will not cause a resubscribe loop.

Props

PropTypeNotes
configShipSilentlyConfigSame options as the JS SDK.
contextUserContextAttributes to evaluate against. Compared by contents.
pollingIntervalMsnumberOptional. Passed through to the stream's polling fallback.
childrenReactNode

Hooks

All three must be called inside <ShipSilentlyProvider>; they throw a descriptive error otherwise.

Hero.tsx
// Three hooks, all of which must be used inside <ShipSilentlyProvider>.
import { useFlag, useFlags, useShipSilentlyClient } from '@shipsilently/react';

// `useFlag` infers its return type from the default — `dark` is boolean,
// `variant` is string. No generic parameters to write.
export function Hero() {
  const dark = useFlag('dark-mode', false);
  const variant = useFlag('hero-variant', 'control');

  return <section className={dark ? 'dark' : 'light'}>{variant}</section>;
}

// `useFlags` gives the whole map plus a first-load indicator. Use `loading` to
// avoid flashing the default value before the first evaluation lands.
export function DebugPanel() {
  const { flags, loading } = useFlags();

  if (loading) return <p>loading flags…</p>;

  return (
    <ul>
      {Object.values(flags).map((f) => (
        <li key={f.flagKey}>
          {f.flagKey}: {String(f.value)} ({f.reason})
        </li>
      ))}
    </ul>
  );
}

// Escape hatch to the underlying client — for `evaluate()` against a one-off
// context, or an explicit analytics flush.
export function FlushButton() {
  const client = useShipSilentlyClient();
  return <button onClick={() => void client.flushAnalytics()}>Flush</button>;
}
HookReturnsNotes
useFlag(key, default)TReturn type inferred from the default. Re-renders on updates.
useFlags(){ flags, loading }loading is true until the first evaluation lands.
useShipSilentlyClient()ShipSilentlyClientEscape hatch to the raw client.
Use loading to avoid a flash of the default. Before the first evaluation resolves, useFlag returns the default you passed. For anything visually disruptive, gate on useFlags().loading and render a skeleton.

Lifecycle

The provider creates exactly one client and closes it on unmount, releasing the stream and any analytics timers. Changing config after mount does not rebuild the client, so read the key and URL from environment variables rather than from state.

Next.js

The package ships with a 'use client' banner, so the provider and hooks are safe to import from Client Components in the App Router.

For Server Components, route handlers, and middleware, don't reach for these hooks, evaluate with the JavaScript/TypeScript SDK directly. A common split is to evaluate server-side for the initial render and mount the provider only for the parts of the tree that need live updates.