> ## Documentation Index
> Fetch the complete documentation index at: https://docs.growthbook.io/llms.txt
> Use this file to discover all available pages before exploring further.

# React SDK

> Use the GrowthBook React SDK to evaluate feature flags and run A/B tests in React, including client and server components.

This is a thin wrapper on top of the [JavaScript Library](https://docs.growthbook.io/lib/js.md), 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 and both client and server components.

**Important**: Starting in version 1.0.0, you must always pass a GrowthBook instance into the GrowthBookProvider. In previous versions, you were allowed to pass `null` as well.

react

React SDK Resources

Get help on Slack

## Installation

Install with a package manager

```bash npm2yarn theme={null}
npm install --save @growthbook/growthbook-react
```

## Quick Usage

### Step 1: Configure your app

```tsx theme={null}
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",
  enableDevMode: true,
  // Only required for A/B testing
  // Called every time a user is put into an experiment
  trackingCallback: (experiment, result, user) => {
    console.log("Experiment Viewed", {
      experimentId: experiment.key,
      variationId: result.key,
      attributes: user?.attributes,
    });
  },
});
gb.init({
  // Optional, enable streaming updates
  streaming: true
})

export default function App() {
  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

```tsx theme={null}
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

```tsx theme={null}
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:

```jsx theme={null}
import { useGrowthBook } from "@growthbook/growthbook-react";

export default function Auth() {
  const growthbook = useGrowthBook();

  const user = useUser();
  useEffect(() => {
    if (!user) return;
    growthbook.setAttributes({
      loggedIn: true,
      id: user.id,
      company: user.company,
      isPro: user.plan === "pro"
    })
  }, [user, growthbook])

  ...
}
```

## Plugins

GrowthBook comes with a number of built-in plugins that add additional functionality. You can enable these by passing them into the GrowthBook constructor.

Plugins require GrowthBook version 1.4.0 or higher, released in Feb 2025.

The React SDK also supports the [tracing plugin](https://docs.growthbook.io/lib/js.md#tracing) for tagging LLM traces in Langfuse or Arize Phoenix with experiment assignments; see [Experimenting on LLM Features](https://docs.growthbook.io/integrations/llm-tracing.md).

### GrowthBook Tracking

Sends experiment view, feature usage, and custom events to GrowthBook's [Managed Warehouse](https://docs.growthbook.io/app/managed-warehouse.md) or [Event Forwarder](https://docs.growthbook.io/app/event-forwarder.md).

Requires `@growthbook/growthbook` v1.4.0 or later. We recommend also adding `autoAttributesPlugin` to include common attributes (browser, `session_id`, UTM parameters, etc.) with every event.

```js theme={null}
import { GrowthBook } from "@growthbook/growthbook";
import {
  autoAttributesPlugin,
  growthbookTrackingPlugin,
} from "@growthbook/growthbook/plugins";

const gb = new GrowthBook({
  clientKey: "YOUR_CLIENT_KEY",
  plugins: [
    autoAttributesPlugin(),
    growthbookTrackingPlugin({
      ingestorHost: "https://us-east-1.gb-ingest.com",
    }),
  ],
});
```

Log custom events with `logEvent`. In React, get the instance with `useGrowthBook()`.

```js theme={null}
// Without properties
gb.logEvent("Page View");

// With properties
gb.logEvent("Button Click", { button: "Sign Up" });
```

| Option | Description |
| - | - |
| `ingestorHost` | Ingestor for your Data Region. |
| `enableFeatureUsageEvents` | Set to `false` to stop sending feature usage events. Default `true`. v1.8.0+. |
| `transport` | `auto` (default), `beacon`, or `fetch`. See below. v1.7.0+. |

Events are batched and sent with `fetch` using `keepalive`. When the page is hidden or unloading, the queue is flushed with `navigator.sendBeacon`. Both survive the page being torn down, so exposures aren't lost when a user navigates away immediately, which matters most for redirect experiments. Set `transport: "beacon"` to always prefer `sendBeacon`, or `"fetch"` to never use it.

### Third Party Tracking

The third-party tracking plugin automatically sends an "Experiment Viewed" event to several popular analytics tracking tools (Segment, Google Analytics, and Google Tag Manager). This plugin requires a browser environment and does not work in Server Components or React Native.

This plugin does not install any 3rd party libraries, it just sends events to ones that are already installed on your site. Below are all of the supported trackers:

* `segment` - Requires `window.analytics` to be defined.
* `gtag` - Requires `window.gtag` to be defined.
* `gtm` - Requires `window.dataLayer` to be defined.

```js theme={null}
import { thirdPartyTrackingPlugin } from "@growthbook/growthbook/plugins";

// Optional settings for the plugin
const pluginOptions = {
  // By default, it will attempt to send to all 3 of these
  trackers: ["segment", "gtag", "gtm"],
  // Additional custom tracking callback
  additionalCallback: (experiment, result) => {
    console.log("Experiment Viewed", {
      experimentId: experiment.key,
      variationId: result.key,
    })
  }
}

const gb = new GrowthBook({
  apiHost: "https://cdn.growthbook.io",
  clientKey: "sdk-abc123",
  plugins: [
    thirdPartyTrackingPlugin(pluginOptions),
  ],
});
```

### Auto-Attributes

The auto-attributes plugin automatically detects and sets common attributes in GrowthBook for the current user. These attributes are available locally for targeting and are not sent to GrowthBook's servers.

This plugin requires a browser environment and does not work in Server Components or React Native.

```js theme={null}
import { autoAttributesPlugin } from "@growthbook/growthbook/plugins";

// Optional settings for the plugin (documented below)
const pluginOptions = {}

const gb = new GrowthBook({
  apiHost: "https://cdn.growthbook.io",
  clientKey: "sdk-abc123",
  plugins: [
    autoAttributesPlugin(pluginOptions),
  ],
});
```

#### Attributes

Here is a list of all attributes that are set by this plugin:

* `id` - A random unique identifier for the user, persisted in a cookie
* `url` - The current URL of the page
* `path` - The current URL path of the page
* `host` - The current URL host of the page
* `query` - The current URL query string of the page
* `pageTitle` - The current page title
* `deviceType` - The device type of the user. Either "mobile" or "desktop"
* `browser` - The browser name of the user (edge, chrome, firefox, safari, or unknown)
* `utmSource`, `utmMedium`, `utmCampaign`, `utmTerm`, `utmContent` - UTM parameters from the URL (persisted in sessionStorage for subsequent page views)

In addition, any variables set through Google Tag Manager (`window.dataLayer`) will be added as attributes as well.

You can still specify your own custom attributes in the GrowthBook constructor, and they will be merged with the auto-attributes.

#### Options

By default the plugin generates a random UUID for the user and stores it in a cookie. You can customize this behavior with the following options.

* `uuidKey` - The attribute key to use for the uuid (default is `id`)
* `uuid` - Pass in your own user id instead of having the plugin generate a random one.
* `uuidCookieName` - The name of the cookie to use for the generated uuid (default is `gbuuid`)
* `uuidCookieDomain` - Scope the uuid cookie to a parent domain (e.g. `".example.com"`) so the same anonymous id is shared across subdomains. Recommended if you run experiments that redirect users between subdomains, otherwise the visitor gets a new id (and can be re-bucketed) on the destination subdomain.
* `uuidAutoPersist` - If `false`, the generated id will NOT be stored in a cookie automatically. Default depends on the `uuid` option. If `uuid` is set, the default is `false`. Otherwise, the default is `true`.

If you have a cookie consent banner, you can set `uuidAutoPersist` to `false` and then later manually trigger the cookie to be saved by calling:

```js theme={null}
document.dispatchEvent(new CustomEvent("growthbookpersist"));
```

## 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`.

```ts theme={null}
const gb = new GrowthBook({
  apiHost: "https://cdn.growthbook.io",
  clientKey: "sdk-abc123",
  // Only required if you have feature encryption enabled in GrowthBook
  decryptionKey: "key_abc123",
});

await gb.init({
  // 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 `init` 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()`.

#### Error Handling

In the case of network issues, the `init` call will not throw an error. Instead, it will stay in the default state where every feature evaluates to `null`.

You can still get access to the error if needed:

```ts theme={null}
const res = await gb.init({
  timeout: 1000
});
console.log(res);
```

The return value has 3 properties:

* **success** - `true` if the GrowthBook instance was populated with features/experiments. Otherwise `false`
* **source** - Where this result came from. One of the following values: `network`, `cache`, `init`, `error`, or `timeout`
* **error** - If success is `false`, this will contain an `Error` object with more details about the error

#### Fallback Payload

To use a bundled payload only when features can't be downloaded, pass it to `setPayload` when `init` doesn't succeed:

```ts theme={null}
const res = await gb.init({ timeout: 2000 });
if (!res.success) {
  await gb.setPayload(fallbackPayload);
}
```

Don't pass the fallback to `init({ payload })` because that skips the network request entirely. A cached payload counts as a success, so the fallback is only used when there's nothing usable in the cache.

The fallback stays in place until a later request succeeds. If `init` timed out and `streaming` or `pollingInterval` is enabled, the SDK applies the original request when it finishes. After a network error, only `pollingInterval` retries.

### Custom Integration

If you prefer to handle the network and caching logic yourself, you can pass in a full JSON "payload" directly into the SDK. 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.

```ts theme={null}
await gb.init({
  payload: {
    features: {
      "feature-1": {...},
      "feature-2": {...},
      "another-feature": {...},
    }
  }
})
```

The data structure for "payload" is exactly the same as what is returned by the GrowthBook SDK endpoints and webhooks.

You can update the payload at any time by calling `setPayload(newPayloadJSON)` and there are also `getPayload()` and `getDecryptedPayload()` methods, which are useful in hybrid apps where you want to hydrate the client with data from the server.

Note: you don't need to specify `clientKey` or `apiHost` on your GrowthBook instance unless you want to enable streaming (see below) or call `refreshFeatures()` later.

#### Synchronous Init

There is an alternate synchronous version of init named `initSync`, which can be especially useful in SSR to prevent hydration mismatches. There are some restrictions/differences:

* You MUST pass in `payload`
* The `payload` MUST NOT have encrypted features or experiments
* If using sticky bucketing, you should use an instance of `StickyBucketServiceSync`, such as `BrowserCookieStickyBucketService`.
* The return value is the GrowthBook instance to enable easy method chaining

## 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.

```jsx theme={null}
<FeaturesReady timeout={500} fallback={<LoadingSpinner/>}>
  <ComponentThatUsesFeatures/>
</FeaturesReady>
```

* `timeout` is the max time you want to wait for features to load (in ms). The default is `0` (no timeout).
* `fallback` is the component you want to display before features are loaded. The default is `null`.

If you want more control, you can use the `useGrowthBook()` hook and the `ready` flag:

```ts theme={null}
const gb = useGrowthBook();

if (gb.ready) {
  // Do something
}
```

## Streaming Updates

The GrowthBook SDK supports streaming with Server-Sent Events (SSE). When enabled, changes to features within GrowthBook will be streamed to the SDK in realtime as they are published. This is only supported on GrowthBook Cloud or if running a GrowthBook Proxy Server.

### Streaming in Browser Environments

SSE is supported on all major browsers, so enabling streaming is as easy as passing `streaming: true` into your `init` call:

```ts theme={null}
gb.init({
  streaming: true,
  // Other settings...
})
```

You may also differentiate your streaming host URL from your API host by setting the `streamingHost` property in the GrowthBook constructor (ex: Remote Evaluation is done on a CDN edge worker while Streaming is done through a GrowthBook Proxy server).

### Streaming in ReactNative

You need to install a polyfill for SSE to use streaming in a ReactNative application:

```bash npm2yarn theme={null}
npm install --save eventsource
```

Then, tell GrowthBook to use this polyfill:

```js theme={null}
const { setPolyfills } = require("@growthbook/growthbook");
// Note the destructuring - `eventsource` v3+ uses a named export
const { EventSource } = require("eventsource");

// Configure GrowthBook to use the eventsource library
setPolyfills({ EventSource });
```

> [!WARNING]
>
> The destructured form above is for `eventsource` v3 and up, which needs Node 18+ (20+ on v4). On v2 and below the constructor is the default export, so use `const EventSource = require("eventsource")` instead. Passing the module object rather than the constructor prevents streaming from starting; the SDK logs a warning when it detects this.

And finally, you can simply pass `streaming: true` into your init calls:

```ts theme={null}
gb.init({
  streaming: true,
  // Other options...
})
```

## Remote Evaluation

> [!TIP]
>
> See the [Remote Evaluation overview](https://docs.growthbook.io/self-host/remote-evaluation.md) for more information about what Remote Evaluation is, how it works, and deployment options.

When used in a front-end context, the React SDK may be run in Remote Evaluation mode. This mode brings the security benefits of a backend SDK to the front end by evaluating feature flags exclusively on a private server. Using Remote Evaluation ensures that any sensitive information within targeting rules or unused feature variations are never seen by the client. Note that Remote Evaluation should not be used in a backend context (Hybrid SSR/CSR is also not supported).

You must enable Remote Evaluation in your SDK Connection settings. Cloud customers are also required to self-host a GrowthBook Proxy Server, edge worker, or custom remote evaluation backend.

To use Remote Evaluation, add the `remoteEval: true` property to your SDK instance. A new evaluation API call will be made any time a user attribute or other dependency changes. You may optionally limit these API calls to specific attribute changes by setting the `cacheKeyAttributes` property (an array of attribute names that, when changed, trigger a new evaluation call).

```ts theme={null}
const gb = new GrowthBook({
  apiHost: "https://gb-proxy.mydomain.io/",
  clientKey: "sdk-abc123",
  // Enable remote evaluation
  remoteEval: true,

  // Optional: only trigger a new evaluation call when the `id` and `email` attributes change
  cacheKeyAttributes: ["id", "email"],
});
```

> [!NOTE]
>
> **Sticky Bucketing with Remote Evaluation**
>
> If you would like to implement Sticky Bucketing while using Remote Evaluation, you must configure your remote evaluation backend to support Sticky Bucketing. In the case of the GrowthBook Proxy Server, this means implementing a Redis database for sticky bucketing use. You will not need to provide a StickyBucketService instance to the client side SDK.

## Caching

The JavaScript SDK has 2 caching layers:

1. In-memory cache (available on all platforms)
2. Persistent localStorage cache (only available in browsers by default)

There are a number of cache settings you can configure within GrowthBook. This must be done BEFORE creating a GrowthBook instance.

Below are all of the default values. You can call `configureCache` with a subset of these fields and the rest will keep their default values.

```ts theme={null}
import { configureCache } from "@growthbook/growthbook";

configureCache({
  // The localStorage key the cache will be stored under
  cacheKey: "gbFeaturesCache",
  // Consider features stale after this much time (60 seconds default)
  staleTTL: 1000 * 60,
  // Cached features older than this will be ignored (4 hours default)
  maxAge: 1000 * 60 * 60 * 4,
  // For Remote Eval only - limit the number of cache entries (~1 entry per user)
  maxEntries: 10,
  // When `false`, we add a `visibilitychange` listener to disable SSE when the page is idle
  disableIdleStreams: false,
  // Consider a page "idle" when it is hidden for this long (default 20 seconds)
  idleStreamInterval: 20000,
  // Set to `true` to completely disable both in-memory and persistent caching
  disableCache: false,
})
```

### Polyfilling localStorage

Outside of a browser environment, you can still use persistent caching. You just need to provide an implementation of the localStorage interface.

Here's an example of using AsyncStorage within ReactNative:

```js theme={null}
import AsyncStorage from '@react-native-async-storage/async-storage';
import { setPolyfills } from "@growthbook/growthbook";

setPolyfills({
  localStorage: {
    getItem: (key) => AsyncStorage.getItem(key),
    setItem: (key, value) => AsyncStorage.setItem(key, value),
  }
});
```

This must be done BEFORE you call either `prefetchPayload` or create the first GrowthBook instance.

## Experimentation (A/B Testing)

In order to run A/B tests, 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.).

```js theme={null}
const gb = new GrowthBook({
  apiHost: "https://cdn.growthbook.io",
  clientKey: "sdk-abc123",
  trackingCallback: (experiment, result, user) => {
    // Example using Segment
    analytics.track("Experiment Viewed", {
      experimentId: experiment.key,
      variationId: result.key,
      attributes: user?.attributes,
    });
  },
});
```

Starting in version 1.7.0, the callback receives a third `user` argument: a `TrackingUserContext` containing the `attributes` and optional `url` used when the experiment was evaluated. We recommend recording attributes from this argument so exposure events consistently reflect the attributes GrowthBook used for targeting and assignment.

This same tracking callback is used for both feature flag experiments and AI Visual Editor experiments.

### Feature Flag Experiments

Evaluating the feature is what enrolls a user and fires `trackingCallback`. See [Feature Flag Experiments](https://docs.growthbook.io/features/feature-flag-experiments.md).

```js theme={null}
// If this has an active experiment and the user is included,
// it will call trackingCallback automatically
useFeatureIsOn("new-signup-form")
```

If the experiment came from a feature rule, `result.featureId` in the trackingCallback will contain the feature id, which may be useful for tracking/logging purposes.

### AI Visual Editor Experiments

Experiments created through the GrowthBook AI Visual Editor will run automatically as soon as their targeting conditions are met.

**Note**: AI Visual Editor experiments are only supported in a web browser environment. They will not run in React Native or during Server Side Rendering (SSR).

If you are using this SDK in a Single Page App (SPA), you will need to let the GrowthBook instance know when the URL changes so the active experiments can update accordingly.

For example, in Next.js, you could do this:

```js theme={null}
function updateGrowthBookURL() {
  gb.setURL(window.location.href);
}

export default function MyApp() {
  // Subscribe to route change events and update GrowthBook
  const router = useRouter();
  useEffect(() => {
    router.events.on("routeChangeComplete", updateGrowthBookURL);
    return () => router.events.off("routeChangeComplete", updateGrowthBookURL);
  }, []);

  // ...
}
```

### URL Redirect Experiments

Similarly to AI Visual Editor experiments, URL redirect tests will run automatically if targeting conditions are met.

If you are using this SDK in a Single Page App (SPA), you'll want to pass in a custom navigation function into the SDK (as default navigation for URL Redirects uses `window.location.replace(url)`) and set the `navigateDelay` to 0.

```js theme={null}
// Example in Next.js
import router from "next/router";

const gb = new GrowthBook({
    navigate: (url) => router.replace(url),
    navigateDelay: 0,
    // ... other settings
});
```

For SPAs you will also need to let the GrowthBook instance know when the URL changes so the active experiments can update accordingly.

```js theme={null}
// Call this every time a navigation event happens in your SPA
function onRouteChange() {
  gb.setURL(window.location.href);
}
```

### Sticky Bucketing

Sticky bucketing ensures that users see the same experiment variant, even when user session, user login status, or experiment parameters change. See the [Sticky Bucketing docs](https://docs.growthbook.io/app/sticky-bucketing.md) for more information. If your organization and experiment support sticky bucketing, you must implement an instance of the `StickyBucketService` to use Sticky Bucketing. The JS SDK exports several implementations of this service for common use cases, or you may build your own:

* `LocalStorageStickyBucketService` —
  For simple bucket persistence using the browser's LocalStorage (can be polyfilled for other environments).

* `BrowserCookieStickyBucketService` — For simple bucket persistence using browser cookies, which are transportable to the back end. Assumes `js-cookie` is implemented (can be polyfilled). Cookie attributes can also be configured. The default cookie expiry is 180 days; override by passing `expires: {days}` into the constructor's `cookieAttributes`. If your site sits behind a Web Application Firewall, see [Sticky Bucket Cookies and WAF False Positives](https://docs.growthbook.io/kb/sdks/sticky-bucket-cookie-encoding.md).

* Build your own — Implement the abstract `StickyBucketService` class and connect to your own data store, or custom wrap multiple service implementations (ex: read/write to both cookies and Redis).

Implementing most StickyBucketService implementations is straightforward and works with minimal setup. For instance, to use the `BrowserCookieStickyBucketService`:

```js theme={null}
import { BrowserCookieStickyBucketService } from "@growthbook/growthbook";
import Cookies from 'js-cookie';

const gb = new GrowthBook({
  apiHost: "https://cdn.growthbook.io",
  clientKey: "sdk-abc123",
  stickyBucketService: new BrowserCookieStickyBucketService({
    jsCookie: Cookies,
  }),
  // ...
});
```

## Next.js

If you are using Next.js, check out our example apps for [Next with App Router](https://github.com/growthbook/examples/tree/main/next-js) and [Next with Pages Router](https://github.com/growthbook/examples/tree/main/next-js-pages).

The examples above show how to use GrowthBook with a number of different rendering strategies and app setups and the App Router example has been updated to support the latest features in Next 14.

## Server Side Rendering (SSR)

This SDK fully supports server side rendering with React.

### React Server Components

If your framework supports the new React Server Components (RSC), welcome to the future! GrowthBook works great with modern React.

First, if you are running experiments with GrowthBook, you will need to fire analytics tracking calls to record which variation a user is assigned. Analytics tools are often only supported client-side, so you can create a small Client Component first:

```tsx theme={null}
"use client";
import { TrackingData } from "@growthbook/growthbook-react";

// Helper component to track experiment views from server components
export default function GrowthBookTracking({ data }: { data: TrackingData[] }) {
  useEffect(() => {
    data.forEach(({ experiment, result }) => {
      console.log("Viewed Experiment", {
        experimentId: experiment.key,
        variationId: result.key
      });
    });
  }, [data])

  return null;
}
```

The React SDK relies on client-side Context, so for Server Components, you need to import our JavaScript SDK `@growthbook/growthbook` instead.

```tsx theme={null}
import { GrowthBook } from "@growthbook/growthbook";
import GrowthBookTracking from "./GrowthBookTracking";

export default async function MyServerPage() {
 // Create and initialize a 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,
  });
  await gb.init({ timeout: 1000 });

  // Set targeting attributes for the user/page
  await gb.setAttributes({
    // TODO: get this from cookies, headers, etc.
    id: cookies().get("my_uuid")?.value || "",
  });

  // Evaluate any feature flags
  const showBanner = gb.isOn("showBanner");
  const title = gb.getFeatureValue("title", "My Site");

  // If the above features ran any experiments, get the tracking call data
  // This is passed into the <GrowthBookTracking> client component below
  const trackingData = gb.getDeferredTrackingCalls();

  // Cleanup
  gb.destroy();

  return (
    <div>
      <h1>{title}</h1>
      {showBanner && (
        <div className="sale">There's a Sale!</div>
      )}

      <GrowthBookTracking data={trackingData} />
    </div>
  );
}
```

#### Hydrating Client Components From the Server

The best part about React Server Components, is that you can easily share feature definitions with your Client Components. By doing this, you avoid any network requests from the browser and any flickering that goes along with that.

Let's first create a GrowthBookWrapper wrapper that takes a `payload` prop and uses it to initialize a GrowthBook instance:

```tsx theme={null}
"use client";
import {
  GrowthBook,
  GrowthBookProvider,
  GrowthBookPayload
} from "@growthbook/growthbook-react";
import { PropsWithChildren, useMemo } from "react";
import Cookies from "js-cookie";

export default function GrowthBookWrapper({
  payload,
  children,
}: PropsWithChildren<{ payload: GrowthBookPayload }>) {
  // Create a singleton GrowthBook instance for this page
  const gb = useMemo(
    () =>
      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,
        trackingCallback: (experiment, result, user) => {
          console.log("Viewed Experiment", {
            experimentId: experiment.key,
            variationId: result.key,
            attributes: user?.attributes,
          });
        },
        // Targeting attributes
        attributes: {
          id: Cookies.get("my_uuid"),
        },
      }).initSync({
        payload,
        // Optional, enable streaming updates
        streaming: true,
      }),
    [payload]
  );

  return <GrowthBookProvider growthbook={gb}>{children}</GrowthBookProvider>;
}
```

Now we'll make a really simple client component using the GrowthBook React SDK:

```tsx theme={null}
"use client";
import { useFeatureIsOn } from "@growthbook/growthbook-react";

export default function OtherComponent() {
  const clientFeature = useFeatureIsOn("client-feature");
  return (
    <p>Client feature: {clientFeature ? "ON" : "OFF"}</p>
  )
}
```

Now, we can render these from our server component:

```tsx theme={null}
import { GrowthBook } from "@growthbook/growthbook";
import GrowthBookWrapper from "./GrowthBookWrapper";
import OtherComponent from "./OtherComponent";
import GrowthBookTracking from "./GrowthBookTracking";

export default async function MyServerPage() {
  // Create and initialize a 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,
  });
  await gb.init({ timeout: 1000 });

  // Set targeting attributes for the user
  gb.setAttributes({
    id: cookies().get("my_uuid")?.value || "",
  });

  // Evaluate any feature flags
  const serverFeature = gb.isOn("server-feature");

  // If the above features ran any experiments, get the tracking call data
  // This is passed into the <GrowthBookTracking> client component below
  const trackingData = gb.getDeferredTrackingCalls();

  // Get the payload to hydrate the client-side GrowthBook instance
  // We need the decrypted payload so the initial client-render can be synchronous
  const payload = gb.getDecryptedPayload();

  // Cleanup your GrowthBook instance
  gb.destroy();

  return (
    <div>
      <p>Server feature: {serverFeature ? "ON" : "OFF"}</p>
      <GrowthBookWrapper payload={payload}>
        <OtherComponent />
      </GrowthBookWrapper>
      <GrowthBookTracking data={trackingData} />
    </div>
  );
}
```

### Traditional SSR

Before React Server Components, each framework implemented their own way to do data fetching and SSR. This example uses Next.js `getServerSideProps` method, but other frameworks should be similar.

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.

```js theme={null}
export const getServerSideProps = async (context) => {
  // Create and initialize a GrowthBook instance
  const gb = new GrowthBook({
    apiHost: process.env.GROWTHBOOK_API_HOST,
    clientKey: process.env.GROWTHBOOK_CLIENT_KEY,
    decryptionKey: process.env.GROWTHBOOK_DECRYPTION_KEY,
  });
  await gb.init({ timeout: 1000 });

  // Set targeting attributes for the user
  await gb.setAttributes({
    id: context?.req?.cookies?.my_uuid || "",
  });

  // Evaluate any feature flags
  const showBanner = gb.isOn("show-banner");
  const title = gb.getFeatureValue("title", "My Site");

  // If the above features ran any experiments, get the tracking call data
  // This is passed into the <GrowthBookTracking> client component below
  const trackingData = gb.getDeferredTrackingCalls();

  // Cleanup
  gb.destroy();

  // Pass the result into your component
  return {
    props: {
      showBanner,
      title,
      trackingData
    }
  }
}

export default function MyPage({ title, showBanner, trackingData }) {
  useEffect(() => {
    trackingData?.forEach(({experiment, result}) => {
      // TODO: Track in your analytics tool
      console.log("Viewed Experiment", {
        experimentId: experiment.key,
        variationId: result.key
      });
    });
  }, [trackingData])

  return (
    <div>
      <h1>{title}</h1>
      {showBanner && (
        <div className="sale">There's a Sale!</div>
      )}
    </div>
  )
}
```

#### Hybrid (SSR + Client-side)

Instead of passing the result of individual feature flags to your component, you can also pass the entire payload. By doing this, you get the benefits of client-side rendering (interactivity, realtime feature flag updates) plus the benefits of SSR (no flickering, improved SEO).

```tsx theme={null}
export const getServerSideProps = async (context) => {
  // Create and initialize a 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,
  });
  await gb.init({ timeout: 1000 });

  // Get the payload to hydrate the client-side GrowthBook instance
  // We need the decrypted payload so the initial client-render can be synchronous
  const payload = gb.getDecryptedPayload();

  // Cleanup
  gb.destroy();

  // Pass the result into your component
  return {
    props: {
      payload
    }
  }
}

export default function MyPage({ payload }) {
  // Create a singleton GrowthBook instance for this page
  const gb = useMemo(
    () =>
      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,
        trackingCallback: (experiment, result, user) => {
          console.log("Viewed Experiment", {
            experimentId: experiment.key,
            variationId: result.key,
            attributes: user?.attributes,
          });
        },
        attributes: {
          id: Cookies.get("my_uuid"),
        },
      }).initSync({
        payload,
        // Optional, enable streaming updates
        streaming: true,
      }),
    [payload]
  );

  return <GrowthBookProvider growthbook={gb}><MyComponent /></GrowthBookProvider>;
}
```

Then, within `MyComponent`, you can use any of the normal client-side hooks or helper components - `useFeatureIsOn`, `useFeatureValue`, etc.

## 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:

1. Feature targeting (e.g. paid users get one value, free users get another)
2. Assigning persistent variations in A/B tests (e.g. user id "123" always gets variation B)

The following are some commonly used attributes, but use whatever makes sense for your application.

```ts theme={null}
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:

```js theme={null}
gb.setAttributes({
  // Only update the `url` attribute, keep the rest the same
  ...gb.getAttributes(),
  url: "/new-page"
})
```

#### Secure Attributes

When *secure attribute hashing* is enabled, all targeting conditions in the SDK payload referencing attributes with datatype `secureString` or `secureString[]` will be anonymized via SHA-256 hashing. This allows you to safely target users based on sensitive attributes. You must enable this feature in your SDK Connection for it to take effect.

If your SDK Connection has secure attribute hashing enabled, you will need to manually hash any `secureString` or `secureString[]` attributes that you pass into the GrowthBook SDK.

To hash an attribute, use a cryptographic library with SHA-256 support, and compute the SHA-256 hashed value of your attribute *plus* your organization's secure attribute salt.

The example below is using CryptoJS ([https://www.npmjs.com/package/crypto-js](https://www.npmjs.com/package/crypto-js)), which provides a synchronous API.

```js theme={null}
import sha256 from 'crypto-js/sha256';

const salt = "f09jq3fij"; // Your secure attribute salt (set in Organization Settings)

// hashing a secureString attribute
const userEmail = sha256(salt + user.email).toString();

// hashing a secureString[] attribute
const userTags = user.tags.map(tag => sha256(salt + tag).toString());

gb.setAttributes({
  id: user.id,
  loggedIn: true,
  email: userEmail,
  tags: userTags,
});

await gb.init();
```

If you prefer to use a browser native implementation, you will need to await hashing before calling `setAttributes` since SubtleCrypto methods are async. Here's an example implementation:

```js theme={null}
// Native browser implementation
async function sha256(str) {
  const buffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(str));
  const hashArray = Array.from(new Uint8Array(buffer));
  return hashArray.map(byte => byte.toString(16).padStart(2, "0")).join("");
}

// Hashing a single string
const userEmail = await sha256(salt + user.email);

// Hashing an array of strings
const userTags = await Promise.all(
  user.tags.map(tag => sha256(salt + tag))
);
```

### 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 New Relic or Datadog.

```ts theme={null}
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](https://chrome.google.com/webstore/detail/growthbook-devtools/opemhndcehfgipokneipaafbglcecjia) 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:

```js theme={null}
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](https://docs.growthbook.io/lib/js.md) for all of the options available for inline experiments

#### useExperiment hook

```tsx theme={null}
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.

```tsx theme={null}
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](https://docs.growthbook.io/tools/cli.md) documentation for more information on generating type definitions and [JavaScript → TypeScript → Strict Typing](https://docs.growthbook.io/lib/js.md#strict-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:

```ts theme={null}
const growthbook = useGrowthBook<AppFeatures>()
```

In that case, the hook will return `GrowthBook<AppFeatures>`.

You can reduce this boilerplate by creating your own hook, e.g.:

```ts theme={null}
// ./src/utils/growthbook.ts
import { useGrowthBook as _useGrowthBook } from "@growthbook/growthbook-react";

export const useGrowthBook = (): GrowthBook<AppFeatures> =>
  _useGrowthBook<AppFeatures>();
```

You can now reference the hook you created instead of the one from the official package:

```ts theme={null}
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.

```ts theme={null}
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:

```ts theme={null}
// ./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:

```ts theme={null}
import { useFeatureIsOn } from "@/src/utils/growthbook"

const isDarkModeOn = useFeatureIsOn("dark_mode");
```

## Examples

* [Next.js ](https://github.com/growthbook/examples/tree/main/next-js)
* [React Native ](https://github.com/growthbook/examples/tree/main/react-native-cli)
* [TypeScript example app with strict typing ](https://github.com/growthbook/examples/tree/main/vanilla-typescript).

## Supported Features
