ReactJS
This is a thin wrapper on top of the Javascript Library, so you might want to view those docs first to familiarize yourself with the basic classes and methods.
This SDK supports both ReactJS and ReactNative environments.
Installation
Install with a package manager
- npm
- Yarn
npm install --save @growthbook/growthbook-react
yarn add @growthbook/growthbook-react
Quick Usage
Step 1: Configure your app
import { useEffect } from "react";
import { GrowthBook, GrowthBookProvider } from "@growthbook/growthbook-react";
// Create a GrowthBook instance
const gb = new GrowthBook({
apiHost: "https://cdn.growthbook.io",
clientKey: "sdk-abc123",
// Enable easier debugging during development
enableDevMode: true,
});
export default function App() {
useEffect(() => {
// Load features from the GrowthBook API
gb.loadFeatures({ autoRefresh: true })
}, []);
useEffect(() => {
// Set user attributes for targeting (from cookie, auth system, etc.)
gb.setAttributes({
id: user.id,
company: user.company,
});
}, [user])
return (
<GrowthBookProvider growthbook={gb}>
<OtherComponent />
</GrowthBookProvider>
);
}
Step 2: Start feature flagging!
There are a few ways to use feature flags in GrowthBook:
Feature Hooks
import { useFeatureValue, useFeatureIsOn } from "@growthbook/growthbook-react";
export default function OtherComponent() {
// Boolean on/off features
const newLogin = useFeatureIsOn("new-login-form");
// String/Number/JSON features with a fallback value
const buttonColor = useFeatureValue("login-button-color", "blue");
if (newLogin) {
return <NewLogin color={buttonColor} />;
} else {
return <Login color={buttonColor} />;
}
}
Feature Wrapper Components
import { IfFeatureEnabled, FeatureString } from "@growthbook/growthbook-react";
export default function OtherComponent() {
return (
<div>
<h1>
<FeatureString feature="site-h1" default="My Site"/>
</h1>
<IfFeatureEnabled feature="welcome-message">
<p>Welcome to our site!</p>
</IfFeatureEnabled>
</div>
);
}
useGrowthBook hook
If you need low-level access to the GrowthBook instance for any reason, you can use the useGrowthBook
hook.
One example is updating targeting attributes when a user logs in:
import { useGrowthBook } from "@growthbook/growthbook-react";
export default function Auth() {
const growthbook = useGrowthBook();
const user = useUser();
useEffect(() => {
if (!user || !growthbook) return;
growthbook.setAttributes({
loggedIn: true,
id: user.id,
company: user.company,
isPro: user.plan === "pro"
})
}, [user, growthbook])
...
}
Loading Features
In order for the GrowthBook SDK to work, it needs to have feature definitions from the GrowthBook API. There are 2 ways to get this data into the SDK.
Built-in Fetching and Caching
If you pass an apiHost
and clientKey
into the GrowthBook constructor, it will handle the network requests, caching, retry logic, etc. for you automatically. If your feature payload is encrypted, you can also pass in a decryptionKey
.
const gb = new GrowthBook({
apiHost: "https://cdn.growthbook.io",
clientKey: "sdk-abc123",
decryptionKey: "key_abc123" // Only if you have feature encryption turned on
});
// Wait for features to be downloaded
await gb.loadFeatures({
// When features change, update the GrowthBook instance automatically
// Default: `false`
autoRefresh: true,
// If the network request takes longer than this (in milliseconds), continue
// Default: `0` (no timeout)
timeout: 2000
});
Until features are loaded, all features will evaluate to null
. If you're ok with a potential flicker in your application (features going from null
to their real value), you can call loadFeatures
without awaiting the result.
If you want to refresh the features at any time (e.g. when a navigation event occurs), you can call gb.refreshFeatures()
.
Custom Integration
If you prefer to handle the network and caching logic yourself, you can instead pass in a features JSON object directly. For example, you might store features in Postgres and send it down to your front-end as part of your app's initial bootstrap API call.
const gb = new GrowthBook({
features: {
"feature-1": {...},
"feature-2": {...},
"another-feature": {...},
}
})
Note that you don't have to call gb.loadFeatures()
. There's nothing to load - everything required is already passed in.
You can update features at any time by calling gb.setFeatures()
with a new JSON object.
Waiting for Features to Load
There is a helper component <FeaturesReady>
that lets you render a fallback component until features are done loading. This works for both built-in fetching and custom integrations.
<FeaturesReady timeout={500} fallback={<LoadingSpinner/>}>
<ComponentThatUsesFeatures/>
</FeaturesReady>
timeout
is the max time you want to wait for features to load (in ms). The default is0
(no timeout).fallback
is the component you want to display before features are loaded. The default isnull
.
If you want more control, you can use the useGrowthBook()
hook and the ready
flag:
const gb = useGrowthBook();
if (gb.ready) {
// Do something
}
Experimentation (A/B Testing)
In order to run A/B tests on your feature flags, you need to set up a tracking callback function. This is called every time a user is put into an experiment and can be used to track the exposure event in your analytics system (Segment, Mixpanel, GA, etc.).
const gb = new GrowthBook({
apiHost: "https://cdn.growthbook.io",
clientKey: "sdk-abc123",
trackingCallback: (experiment, result) => {
// Example using Segment
analytics.track("Experiment Viewed", {
experimentId: experiment.key,
variationId: result.key,
});
},
});
Once you set that up, just use feature flags like normal in your code. If an experiment is used to determine the feature flag value, it will automatically call your tracking callback.
// If this has an active experiment, it will call trackingCallback automatically
const newLogin = useFeatureIsOn("new-signup-form");
Server Side Rendering (SSR)
This SDK fully supports server side rendering. The below examples use Next.js, but other frameworks should be similar.
First, create a helper function:
import { setPolyfills } from "@growthbook/growthbook-react";
export function getServerSideGrowthBookContext(req) {
// Set GrowthBook polyfills for server environments
setPolyfills({
fetch: globalThis.fetch || require("cross-fetch"),
EventSource: globalThis.EventSource || require("eventsource"),
SubtleCrypto: globalThis.crypto?.subtle || require("node:crypto")?.webcrypto?.subtle
});
return {
apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
decryptionKey: process.env.NEXT_PUBLIC_GROWTHBOOK_DECRYPTION_KEY,
attributes: {
// TODO: get more targeting attributes from request context
id: (req && req.cookies.DEVICE_ID) ?? null,
},
}
}
There are 2 ways to use GrowthBook for SSR.
Pure SSR
With this approach, feature flags are evaluated once when the page is rendered. If a feature flag changes, the user would need to refresh the page to see it.
export const getServerSideProps = async (context) => {
// Create a GrowthBook instance and load features from the API
const gbContext = getServerSideGrowthBookContext(context);
const gb = new GrowthBook(gbContext);
await gb.loadFeatures({ timeout: 1000 });
return {
props: {
title: gb.getFeatureValue("site-title", "fallback"),
showBanner: gb.isOn("sale-banner")
}
}
}
export default function MyPage({ title, showBanner }) {
return (
<div>
<h1>{title}</h1>
{showBanner && (
<div className="sale">There's a Sale!</div>
)}
</div>
)
}
Hybrid (SSR + Client-side)
With this approach, you use the client-side hooks and components (e.g. useFeatureValue
) and simply use SSR to make sure the initial load already has the latest features from the API.
You get the benefits of client-side rendering (interactivity, realtime feature flag updates) plus the benefits of SSR (no flickering, improved SEO).
First, follow the normal setup steps for client-side rendering:
// pages/_app.jsx
import { useEffect } from "react";
import { GrowthBook, GrowthBookProvider } from "@growthbook/growthbook-react";
// Create a client-side GrowthBook instance
const gb = new GrowthBook({
apiHost: process.env.NEXT_PUBLIC_GROWTHBOOK_API_HOST,
clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
decryptionKey: process.env.NEXT_PUBLIC_GROWTHBOOK_DECRYPTION_KEY,
enableDevMode: true,
});
export default function App() {
useEffect(() => {
// Load features from the GrowthBook API and keep them up-to-date
gb.loadFeatures({ autoRefresh: true })
}, []);
useEffect(() => {
// Set user attributes for targeting (use the same values as SSR when possible)
gb.setAttributes({
id: user.id,
});
}, [user])
// Wrap your app in a GrowthBookProvider
return (
<GrowthBookProvider growthbook={gb}>
<OtherComponent />
</GrowthBookProvider>
);
}
Then, use the useGrowthBookSSR
hook to enable server side rendering:
// pages/server.jsx
import MyComponent from "../components/MyComponent";
import { generateGrowthBookSSRProps } from "../util/gb-server";
import { useGrowthBookSSR } from "@growthbook/growthbook-react";
export const getServerSideProps = async (context) => {
const gbContext = getServerSideGrowthBookContext(context);
const gbData = await getGrowthBookSSRData(gbContext);
return {
props: {
gbData,
},
};
};
export default function ServerPage({ gbData }) {
// This is required once at the top of the SSR page
useGrowthBookSSR(gbData);
return <MyComponent />;
}
Lastly, in the rest of your app, use the client-side hooks and components just as you would if you weren't using SSR.
// components/MyComponent.jsx
export default function MyComponent() {
// Boolean on/off features
const newLogin = useFeatureIsOn("new-login-form");
// String/Number/JSON features with a fallback value
const buttonColor = useFeatureValue("login-button-color", "blue");
if (newLogin) {
return <NewLogin color={buttonColor} />;
} else {
return <Login color={buttonColor} />;
}
}
These exact same approaches work for static pages as well!
API Reference
There are a number of configuration options and settings that control how GrowthBook behaves.
Attributes
You can specify attributes about the current user and request. These are used for two things:
- Feature targeting (e.g. paid users get one value, free users get another)
- Assigning persistent variations in A/B tests (e.g. user id "123" always gets variation B)
The following are some comonly used attributes, but use whatever makes sense for your application.
new GrowthBook({
attributes: {
id: "123",
loggedIn: true,
deviceId: "abc123def456",
company: "acme",
paid: false,
url: "/pricing",
browser: "chrome",
mobile: false,
country: "US",
},
});
Updating Attributes
If attributes change, you can call setAttributes()
to update. This will completely overwrite any existing attributes. To do a partial update, use the following pattern:
gb.setAttributes({
// Only update the `url` attribute, keep the rest the same
...gb.getAttributes(),
url: "/new-page"
})
Feature Usage Callback
GrowthBook can fire a callback whenever a feature is evaluated for a user. This can be useful to update 3rd party tools like NewRelic or DataDog.
new GrowthBook({
onFeatureUsage: (featureKey, result) => {
console.log("feature", featureKey, "has value", result.value);
},
});
Note: If you evaluate the same feature multiple times (and the value doesn't change), the callback will only be fired the first time.
Dev Mode
There is a GrowthBook Chrome DevTools Extension that can help you debug and test your feature flags in development.
In order for this to work, you must explicitly enable dev mode when creating your GrowthBook instance:
const gb = new GrowthBook({
enableDevMode: true,
});
To avoid exposing all of your internal feature flags and experiments to users, we recommend setting this to false
in production in most cases.
Inline Experiments
Depending on how you configure feature flags, they may run A/B tests behind the scenes to determine which value gets assigned to the user.
Sometimes though, you want to run an inline experiment without going through a feature flag first. For this, you can use either the useExperiment
hook or the Higher Order Component withRunExperiment
:
View the Javascript SDK Docs for all of the options available for inline experiments
useExperiment hook
import { useExperiment } from "@growthbook/growthbook-react";
export default function OtherComponent() {
const { value } = useExperiment({
key: "new-headline",
variations: ["Hello", "Hi", "Good Day"]
});
return <h1>{value}</h1>;
}
withRunExperiment (class components)
Note: This library uses hooks internally, so still requires React 16.8 or above.
import { withRunExperiment } from "@growthbook/growthbook-react";
class OtherComponent extends React.Component {
render() {
// The `runExperiment` prop is identical to the `useExperiment` hook
const { value } = this.props.runExperiment({
key: "headline-test",
variations: ["Hello World", "Hola Mundo"]
});
return <h1>{value}</h1>;
}
}
// Wrap your component in `withRunExperiment`
export default withRunExperiment(OtherComponent);
TypeScript support
Some hooks are available in type-safe versions. These require you to pass in your generated types as the generic argument.
See the GrowthBook CLI documentation for more information on generating type definitions and JavaScript → TypeScript → Scrict Typing for how to use them.
useGrowthBook<T>()
A type-safe version of the useGrowthBook()
hook is available. Everywhere you use useGrowthBook()
, pass the generated features as the generic argument:
const growthbook = useGrowthBook<AppFeatures>()
In that case, the hook will return GrowthBook<AppFeatures> | undefined
.
You can reduce this boilerplate by creating your own hook, e.g.:
// ./src/utils/growthbook.ts
import { useGrowthBook as _useGrowthBook } from "@growthbook/growthbook-react";
export const useGrowthBook = (): GrowthBook<AppFeatures> | undefined =>
_useGrowthBook<AppFeatures>();
You can now reference the hook you created instead of the one from the official package:
import { useGrowthBook } from "@/src/utils/growthbook"
const growthbook = useGrowthBook();
growthbook?.getFeatureValue(knownKey, defaultValueOfValidType)
useFeatureIsOn<T>()
The React SDK also provides access to a type-safe useFeatureIsOn<AppFeatures>()
hook.
const isDarkModeOn = useFeatureIsOn<AppFeatures>("dark_mode");
This will only allow you to pass known keys to the hook.
You can reduce the boilerplate for this hook by creating your own and using that instead:
// ./src/utils/growthbook.ts
import { useFeatureIsOn as _useFeatureIsOn } from "@growthbook/growthbook-react";
export const useFeatureIsOn = (id: keyof AppFeatures & string): boolean =>
_useFeatureIsOn<AppFeatures>(id);
And then reference the hook you created instead of the one from the official package:
import { useFeatureIsOn } from "@/src/utils/growthbook"
const isDarkModeOn = useFeatureIsOn("dark_mode");