Skip to content

React SDK

The RocketFlag React SDK wraps the RocketFlag API in idiomatic React primitives — a provider, a hook, and a declarative component — so you get loading/error state and re-renders on resolution without hand-rolling effects.

It is built on browser-native fetch with zero runtime dependencies (React is a peer dependency).

Terminal window
npm install @rocketflag/react-sdk

Requires React 16.14 or newer.

Add RocketFlagProvider once, near the root of your app. It builds a single shared client (and shared cache) for every flag check below it.

import { RocketFlagProvider } from "@rocketflag/react-sdk";
function Root() {
return (
<RocketFlagProvider cacheTtlSeconds={300}>
<App />
</RocketFlagProvider>
);
}

Provider props (all optional):

PropDescription
versionAPI version segment (defaults to v1).
apiUrlBase API URL (defaults to https://api.rocketflag.app).
cacheTtlSecondsDefault cache TTL in seconds, shared across all hooks. Omit to disable caching.
cacheMaxEntriesMaximum number of cached flag responses (defaults to 10000). Evicts least recently used entry when full.
clientInject a pre-built client (testing / advanced use).
import { useFlag } from "@rocketflag/react-sdk";
function SignUpButton() {
const { enabled, loading, error } = useFlag("IFldMzqP5jtv9wAL");
if (loading) return <Spinner />;
if (error) return null;
return enabled ? <NewSignUp /> : <OldSignUp />;
}

useFlag returns:

FieldTypeDescription
enabledbooleanflag?.enabled ?? false.
flagFlagStatus | nullThe full flag, or null until resolved.
loadingbooleantrue until the first fetch settles.
errorError | nullThe thrown error, if any.
refetch() => voidRe-run the fetch on demand.

useFlag must be called inside a <RocketFlagProvider>. Used outside one, it throws useFlag must be used within a <RocketFlagProvider>.

Use refetch to re-evaluate on demand — for example after a user action that may change targeting:

const { enabled, refetch } = useFlag("IFldMzqP5jtv9wAL");
return <button onClick={refetch}>Re-check feature</button>;
import { Flag } from "@rocketflag/react-sdk";
<Flag id="IFldMzqP5jtv9wAL" fallback={<OldBanner />} loading={<Spinner />}>
<NewBanner />
</Flag>;

children render when the flag is enabled; fallback when it’s disabled or the fetch errors; loading while it’s pending. Both fallback and loading default to rendering nothing.

Pass a user context as the second argument. The cache keys on flag ID and context, so different cohorts/environments resolve independently.

const { enabled } = useFlag("IFldMzqP5jtv9wAL", { cohort: "beta", env: "staging" });
  • cohort: string | number | boolean — cohort/variant identifier.
  • env: string, letters, numbers, hyphens, and underscores (e.g. prod-portals, staging_v2). Required for Group Flags.
  • targetingKey: a stable user identifier that makes percentage rollouts sticky. See Attributes and sticky rollouts.
  • any other key: an audience attribute, such as plan or country.

Input validation: the client validates its arguments and throws synchronously for bad input: flagId must be a non-empty string, context values must be string/number/boolean, and env must contain only alphanumeric characters, hyphens, or underscores. These surface via the hook’s error field.

Add a targetingKey for sticky percentage rollouts, and any other keys as audience attributes, in the same context object:

const { enabled } = useFlag("IFldMzqP5jtv9wAL", {
targetingKey: user.id,
plan: "pro",
country: "AU",
});

Percentage rollouts are sticky per key: the same targetingKey (or, without one, the same cohort) gets the same answer every time, so send a stable user identifier. Other keys are matched against the flag’s audience and are only read when the flag has one. Matching is exact and case-sensitive, and an attribute you do not send never matches. cohort, env and targetingKey are reserved and cannot be audience attributes. The SDK sends every key in the context to the API, so this needs no new method. See Sticky rollouts. With caching on, each distinct targetingKey is a separate cache entry.

In TypeScript, UserContext declares targetingKey, cohort, env, and accepts any other key as an audience attribute whose value is a ContextValue (string | number | boolean), so you no longer need a cast to use sticky percentage rollouts or audiences. Values cannot be undefined, so leave out any keys you do not have. Passing { plan: user.plan } where user.plan may be undefined is a compile error. TypeScript cannot see through an optional property on an object you have typed yourself, so that case throws at runtime instead. Because UserContext is defined with a string index signature, a context typed with a TypeScript interface will not implicitly type-check; declare custom context types using a type alias instead, or spread the object (useFlag(id, { ...context })).

Pass call options as the third argument to override the provider default for a single call (or 0 to force a fresh fetch):

const { flag } = useFlag("IFldMzqP5jtv9wAL", {}, { ttlSeconds: 0 });

Need a client outside of React (e.g. in an event handler or loader)? Build one directly:

import { createRocketflagClient } from "@rocketflag/react-sdk";
const client = createRocketflagClient(); // (version?, apiUrl?, { ttlSeconds, maxEntries }?)
const flag = await client.getFlag("IFldMzqP5jtv9wAL");

The underlying client throws the same three error types as the Node SDK:

  • APIError — the API returned a non-ok response (carries status and statusText).
  • InvalidResponseError — the response wasn’t valid JSON or didn’t match the expected shape.
  • NetworkError — a network failure, such as a dropped connection.

These surface via the error field on useFlag. You can narrow them:

import { APIError, InvalidResponseError, NetworkError } from "@rocketflag/react-sdk";
const { error } = useFlag("IFldMzqP5jtv9wAL");
if (error instanceof APIError) {
// error.status, error.statusText
}

Inject a pre-built (or mock) client via the provider’s client prop. When set, version, apiUrl, cacheTtlSeconds, and cacheMaxEntries are ignored, so your components resolve flags without hitting the network:

const mockClient = {
getFlag: async () => ({ id: "test", name: "Test Flag", enabled: true }),
};
render(
<RocketFlagProvider client={mockClient}>
<SignUpButton />
</RocketFlagProvider>,
);

Flag fetching runs client-side inside useEffect, so no request is made during SSR — the server render produces the loading state, and the real value resolves after hydration. This keeps the SDK safe to drop into server-rendered apps without leaking requests onto the server.

  • Caching is opt-in — without cacheTtlSeconds (or a per-call ttlSeconds), every check hits the API.
  • The cache holds at most 10,000 entries by default and evicts the least recently used entry when full. You can customize this limit using the cacheMaxEntries provider prop or the maxEntries client cache option.
  • No in-flight de-duplication: two components requesting the same uncached flag in the same tick may each fire a request before the cache populates.
  • The API is one-flag-per-request, so this SDK offers useFlag(id) rather than a bulk useFlags().

The package is written in TypeScript and ships its own declarations. Everything you can import:

import {
RocketFlagProvider, // provider component
useFlag, // hook
Flag, // declarative component
createRocketflagClient, // imperative client factory
APIError, // error classes
NetworkError,
InvalidResponseError,
} from "@rocketflag/react-sdk";
import type {
RocketFlagProviderProps,
UseFlagResult,
FlagProps,
FlagStatus,
UserContext,
ContextValue,
CacheOptions,
CallOptions,
RocketFlagClient,
} from "@rocketflag/react-sdk";

The resolved flag shape (FlagStatus) matches the API response:

type FlagStatus = {
id: string;
name: string;
enabled: boolean;
};