Quickstart by stack

React Quickstart

Install the React SDK, wrap your app in FlagsProvider, and read flags with useFlag, useVariant, and FlagGate.

Prerequisites

  • React 18.2 or newer (React 19 works too) and @zenmanage/sdk 3.5 or newer.
  • A client key (prefixed cli_) stored in build-time configuration. Server and mobile keys are not valid for the browser.
  • A flag key you can test in a non-production environment.
Install bash

npm install @zenmanage/react @zenmanage/sdk
Wrap your app and read a flag tsx

import { FlagsProvider, useFlag } from '@zenmanage/react';

function CheckoutButton() {
  const { value: enabled, isLoading } = useFlag('new-checkout-button', false);

  if (isLoading) {
    return <button disabled>Loading...</button>;
  }

  return <button>{enabled ? 'Checkout (New)' : 'Checkout'}</button>;
}

export function App() {
  return (
    <FlagsProvider environmentToken={import.meta.env.VITE_ZENMANAGE_CLIENT_KEY}>
      <CheckoutButton />
    </FlagsProvider>
  );
}

Safe defaults, explicit loading

Every hook takes a default and returns it until the flag resolves, and again if evaluation fails. isLoading and error are explicit, so you decide what the UI does in each state. The type of the default decides how the flag is read: boolean, string, number, or JSON.

Built on the JavaScript SDK

The package is a thin wrapper around @zenmanage/sdk, so caching, context handling, and defaults behave the same way they do in the JavaScript quickstart. Pass your own client to the provider if you already have one.

Target a user with context tsx

import { Attribute, Context } from '@zenmanage/sdk';
import { FlagsProvider, useFlag } from '@zenmanage/react';

function PremiumBanner() {
  const { value } = useFlag('premium-banner', false);
  return value ? <div>Premium Banner</div> : null;
}

export function App({ user }: { user: { id: string; name: string; plan: string } }) {
  const context = new Context('user', user.name, user.id, [new Attribute('plan', [user.plan])]);

  return (
    <FlagsProvider environmentToken={import.meta.env.VITE_ZENMANAGE_CLIENT_KEY} context={context}>
      <PremiumBanner />
    </FlagsProvider>
  );
}
Run an A/B test with useVariant tsx

import { useVariant } from '@zenmanage/react';

function CheckoutExperience() {
  const { variant, isLoading } = useVariant('checkout-flow', 'control');

  if (isLoading) {
    return <p>Loading checkout...</p>;
  }

  if (variant === 'one-page') {
    return <OnePageCheckout />;
  }

  return <MultiPageCheckout />;
}
Gate a component with FlagGate tsx

import { FlagGate } from '@zenmanage/react';

<FlagGate flagKey="beta-chat" disabledFallback={<LegacyChat />}>
  <BetaChat />
</FlagGate>
Read a JSON value tsx

const { value: ui } = useFlag('ui-config', { theme: 'light', pageSize: 20 });
const { value: steps } = useFlag<string[]>('onboarding-steps', []);

Client keys only in the browser

Use a cli_ key in the browser. A srv_ key throws there, and a cli_ key throws in Node.js. Never ship a server key to the browser — read it from a server-only environment variable.

Server-side rendering

Effects don't run on the server, so hooks render their defaults with isLoading set to true and flags load after hydration. If you need real values in the first paint, fetch them on the server with @zenmanage/sdk and pass them down as props.

Next step

Take the next integration step in your own stack.

Start with the quickstart that matches your runtime, then return to the reference pages when you need exact request and payload details.

Language and framework names and logos shown above are trademarks or registered trademarks of their respective owners. Their use here is for identification purposes only and does not imply endorsement of, or affiliation with, Zenmanage.