Skip to main content
Version: 2.0.0

ReactJS Client

Github: https://github.com/featureflow/react-featureflow-client

react-featureflow-client is the official React binding over the Featureflow Javascript Client. It gives you a provider for the app root and hooks for your components. It bundles featureflow-client, so there is nothing else to install.

Uses a client SDK key (sdk-js-env-…), which is public by design. Find it on the Projects page by clicking the key icon next to your environment.

note

Version 2.x uses the React context API and requires React 16.3 or later. If you are on the 1.x client, see Migrating from 1.x.

Installation​

npm install --save react-featureflow-client

Getting started​

1. Wrap your app in one provider​

There should be exactly one provider, at the top of your component tree. The recommended option is asyncFeatureflowProvider, which initialises the client before React renders, so there is no flash of the wrong variant:

// index.tsx
import ReactDOM from 'react-dom/client';
import { asyncFeatureflowProvider } from 'react-featureflow-client';
import App from './App';

const user = {
id: 'user-123',
attributes: {
tier: 'gold',
country: 'australia'
}
};

const initApp = async () => {
const FeatureflowProvider = await asyncFeatureflowProvider({
apiKey: 'sdk-js-env-YOUR_KEY_HERE',
user
});

ReactDOM.createRoot(document.getElementById('root')!).render(
<FeatureflowProvider>
<App />
</FeatureflowProvider>
);
};

initApp();

2. Read a flag with useFeature​

useFeature(key) evaluates one feature and returns the full evaluation — isOn(), isOff(), is(variant), value() and jsonValue(). The component re-renders automatically whenever the flag changes, including when the user is updated.

// Checkout.tsx
import { useFeature } from 'react-featureflow-client';

function Checkout() {
const newCheckout = useFeature('new-checkout');

if (newCheckout.isOn()) {
return <NewCheckout />;
}
return <LegacyCheckout />;
}

Multivariate flags work the same way:

function PricingPage() {
const pricing = useFeature('pricing-layout');

if (pricing.is('compact')) return <CompactPricing />;
if (pricing.is('detailed')) return <DetailedPricing />;
return <DefaultPricing />;
}
Prefer useFeature in render

useFeatureflow().evaluate(key) reads the value once at render time and will not re-render your component when the flag changes underneath it. useFeature subscribes to updates. Reach for the client directly only for imperative work such as updateUser.

Likewise, do not copy a flag value into useState — that snapshots it and defeats live updates.

That is all a typical app needs. The rest of this page covers the other providers and hooks.

Providers​

Shown above. Resolves to a provider component once the first evaluation has landed. The cost is a short delay before first paint; if your app already shows a splash or skeleton this is the right trade.

const FeatureflowProvider = await asyncFeatureflowProvider({
apiKey: 'sdk-js-env-YOUR_KEY',
user: { id: 'user-123', attributes: { plan: 'premium' } },
config: { offline: false } // optional
});

FeatureflowProvider​

Initialises the client in useEffect after mount. Simpler, but flag-driven UI above the fold can flicker on first load. Use it when flags only affect UI below the fold or behind an interaction.

import { FeatureflowProvider } from 'react-featureflow-client';

<FeatureflowProvider
apiKey="sdk-js-env-YOUR_KEY"
user={{ id: 'user-123', attributes: { plan: 'premium' } }}
config={{ offline: false }}
>
<App />
</FeatureflowProvider>

FeatureflowProviderWithClient​

For an existing client instance, for example one shared with non-React code:

import Featureflow from 'featureflow-client';
import { FeatureflowProviderWithClient } from 'react-featureflow-client';

const client = await Featureflow.init('sdk-js-env-YOUR_KEY', user);

<FeatureflowProviderWithClient client={client}>
<App />
</FeatureflowProviderWithClient>

Whichever you pick, use one provider at the app root. Nested providers mean two clients, two sets of impression events, and components that disagree about the same flag.

Provider props​

PropTypeRequiredDescription
apiKeystringYesYour client SDK key (sdk-js-env-…)
userFeatureflowUserNoUser context for targeting: { id, attributes }
configConfigNoClient configuration, forwarded to featureflow-client

Hooks​

import {
useFeature,
useFeatures,
useFeatureflow,
useJsonValue,
useTrack
} from 'react-featureflow-client';
HookReturns
useFeature(key)The full evaluation for one feature: isOn(), isOff(), is(variant), value(), jsonValue(). Re-renders on change.
useFeatures()Every evaluated feature as { key: variant }. Re-renders on change.
useFeatureflow()The client itself, for updateUser and other imperative use.
useJsonValue<T>(key)The evaluated variant's JSON config payload, or undefined.
useTrack()A stable track(goalKey, details?) function for goal events.

useFeatures​

Useful for debug panels or when a component depends on several flags:

const features = useFeatures();
// { 'new-checkout': 'on', 'pricing-layout': 'compact', ... }

useTrack​

Track a goal against the current user. details is either a number (the metric value) or an object whose optional value is the metric value and whose remaining properties are sent as custom data.

import { useFeature, useTrack } from 'react-featureflow-client';

function Checkout() {
const newCheckout = useFeature('new-checkout');
const track = useTrack();

if (newCheckout.isOn()) {
return <NewCheckout onComplete={() => track('purchase', { value: 49.95 })} />;
}
return <LegacyCheckout onComplete={() => track('purchase', { value: 49.95 })} />;
}

JSON configuration values with useJsonValue​

Requires react-featureflow-client >= 2.2.0 (which depends on featureflow-client >= 2.2.0) — see SDK Compatibility.

If a variant has a JSON config value set in the dashboard, useJsonValue reads it directly:

import { useJsonValue } from 'react-featureflow-client';

interface ThemeConfig {
color: string;
layout: string;
}

function CheckoutButton() {
const theme = useJsonValue<ThemeConfig>('checkout-theme');

return (
<button style={{ background: theme?.color ?? '#000000' }}>
Checkout
</button>
);
}

useJsonValue returns undefined if the resolved variant has no JSON config set, so always provide a fallback. If you need the variant and the payload together, useFeature exposes the same thing through jsonValue():

function CheckoutButton() {
const evaluation = useFeature('checkout-theme');
const theme = evaluation.jsonValue<ThemeConfig>();

return (
<button style={{ background: theme?.color ?? '#000000' }}>
{evaluation.isOn() ? 'Checkout' : 'Unavailable'}
</button>
);
}

Updating the user​

Call updateUser on login, on logout, and whenever an attribute used in targeting changes. Every component using useFeature or useFeatures re-renders with the new values:

import { useFeatureflow } from 'react-featureflow-client';

const featureflow = useFeatureflow();

await featureflow.updateUser({
id: user.id,
attributes: { tier: user.tier, beta: user.inBetaProgram }
});

Naming your application​

Optionally name this app so the dashboard can attribute SDK usage and flag evaluations to it, by passing application in the provider's config (it is forwarded to the underlying Javascript client):

<FeatureflowProvider
apiKey="sdk-js-env-YOUR_KEY"
user={user}
config={{ application: 'web-app' }}
>
<App />
</FeatureflowProvider>

See Application Tags for the naming rules and what the tag powers.

Testing components​

Wrap the component under test in a provider configured offline. No network, deterministic variants, and both branches are testable by flipping defaultFeatures:

<FeatureflowProvider
apiKey="test"
config={{ offline: true, defaultFeatures: { 'new-checkout': 'on' } }}
>
<Checkout />
</FeatureflowProvider>

Write a test for each branch — an untested flag branch is the usual reason a rollout gets reverted.

TypeScript​

The package ships its own definitions:

import type {
FeatureflowUser,
FeatureflowClient,
Config,
EvaluatedFeatures,
Evaluate
} from 'react-featureflow-client';

Migrating from 1.x​

  • Use asyncFeatureflowProvider or FeatureflowProvider instead of withFeatureflowProvider.
  • Use the hooks (useFeature, useFeatures, useFeatureflow) instead of the withFeatureflow HOC.
  • featureflow-client is bundled, so remove it from your own dependencies unless other code uses it directly.

Further reading​