← Blog

Feature flags in React: a practical guide

To add feature flags in React, install the dif SDK and its React adapter, wrap your app in a provider, and read a flag with the useDif hook. Assignment is deterministic and computed locally, so a user never flickers between variants across renders and there is no network call to decide which one they get.

Two things usually go wrong with feature flags in React. The flag lives in a dashboard, disconnected from the component it gates, and the SDK makes a network request to evaluate it, which adds latency and can flash the wrong variant before the real one loads. dif takes a different path: the flag is a Markdown file in your repo, compiled to a typed client, and evaluated as a pure function of the user id. This guide walks through installing it, gating a component, running an A/B test, and rendering on the server without a flicker.

Key Takeaways

  • Add feature flags in React with two packages, @dif.sh/sdk and @dif.sh/react, wired up with a <DifProvider> at the root of your tree.
  • Read a flag anywhere with the useDif hook, which returns { track, exposure }. The exposure call gates the component.
  • Assignment is deterministic and local, a hash of the user id, so there is no network call and no flicker between renders.
  • The same call runs an A/B test: pass a map of variants and dif returns the one this user gets.
  • Because the variant is a pure function of the user id, server and client compute the same value, so server-side rendering does not flash the wrong variant.

Install and initialize

Install the SDK and the React adapter. The SDK is a peer dependency, so add both:

npm install @dif.sh/sdk @dif.sh/react

Wrap your app in <DifProvider> once, at the root of the tree. It runs dif.init a single time and makes the flag state available to every component below it, the same way any React context provider does:

import { DifProvider } from "@dif.sh/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <DifProvider
      config={{
        project: "acme-shop",
        userId: () => currentUser?.id ?? null,
      }}
    >
      {children}
    </DifProvider>
  );
}

The userId resolver is how a user is assigned a variant. Add a publishableKey to send exposure and outcome events to dif Cloud for analysis; assignment itself works without it, because it is computed locally. The flags come from your repo: dif init scaffolds a dif/ directory, and dif build compiles the active flag files into the typed client the SDK reads.

Gate a component with useDif

Read a flag anywhere below the provider with the useDif hook. For a simple on/off flag, compare the variant:

import { useDif } from "@dif.sh/react";

export function Checkout() {
  const { exposure: dif } = useDif();
  if (dif("new-checkout") === "on") {
    return <NewCheckout />;
  }
  return <LegacyCheckout />;
}

exposure has the same signature as the bare dif() from the SDK, and calling it records that this user saw the flag, once per session. That is the only wiring. No network request fires to decide the variant, because dif computed it from the user id.

Run an A/B test in the same component

A flag and an experiment are the same call. Instead of comparing a string, pass a map of variants, and dif returns the one this user gets:

import { useDif } from "@dif.sh/react";

export function CheckoutCTA() {
  const { exposure: dif } = useDif();
  const label = dif("checkout-cta-v2", {
    control: () => "Place order",
    variant_a: () => "Get it today",
  });
  return <button>{label()}</button>;
}

You record the outcome you are measuring with track, fired where it actually happens, for example when checkout completes:

const { track } = useDif();
// after a successful checkout:
track("completed_checkout", { value: 49 });

When the experiment concludes and variant_a wins, you ramp it by editing the weights in the flag file, and this component does not change. The A/B testing for developers guide covers the experiment lifecycle.

Server-side feature flags in React, without a flicker

The flash of the wrong variant is the classic feature-flag bug in React: the server renders one thing, the client hydrates another, and the UI jumps. It happens when assignment is non-deterministic or depends on a client-only fetch.

dif avoids it because assignment is a pure function of the user id. The same user id produces the same variant every time, on the server and in the browser, so the markup matches and there is nothing to reconcile. In a Next.js App Router app, resolve the same userId on the server as on the client, and the server-rendered variant is the one the client keeps. For a server component or a route handler, call the bare dif() from @dif.sh/sdk with the user id and render the result directly.

Why the React SDK stays small

The dif React SDK is about 5 kB gzipped with zero dependencies. There is no evaluation service to call and no assignment database, because the variant is computed from the user id with a hash. That is what keeps it small and removes the network round-trip a dashboard-based SDK makes on load.

dif build compiles your active flags into a typed client, so dif("new-checkout") is checked against the real flag ids at build time. A flag that does not exist is a type error, not a silent miss in production. The SDK docs cover the adapter, and feature flags in git explains why the flag is a file in the first place.

Getting started

Install the CLI, scaffold a project, then add the React packages:

npm install -g @dif.sh/cli
dif init
npm install @dif.sh/sdk @dif.sh/react

dif init writes the dif/ directory and the generated client, dif new drafts a flag, and dif build compiles it. What feature flags are is the primer, and the feature flags page shows the workflow end to end.

A feature flag in React comes down to one provider and one hook. The flag lives in your repo, compiles to a typed client, and evaluates locally, so the component that reads it stays simple and the user never sees the wrong variant.

FAQ

How do I add feature flags to a React app? Install @dif.sh/sdk and @dif.sh/react, wrap your app in <DifProvider> with a userId resolver, and read a flag with the useDif hook. The flags live in your repo and compile to a typed client, so there is no dashboard and no network call to evaluate one.

Is there a React hook for feature flags? Yes. useDif() returns { track, exposure }. Use exposure (aliased to dif) to read a flag or run an experiment, and track to record an outcome. It works in any component under the provider.

Do dif feature flags work with Next.js? Yes. Put <DifProvider> in the root layout for client components. Because assignment is deterministic, you can also call the bare dif() from @dif.sh/sdk in a server component or route handler and get the same variant, so server and client agree.

How do feature flags avoid flicker with server-side rendering? Assignment is a pure function of the user id, so the server and the client compute the same variant. The server-rendered markup matches what the client hydrates, so there is no flash of the wrong variant, as long as you resolve the same user id on both sides.

How big is the dif React SDK? About 5 kB gzipped with zero dependencies. There is no network call to assign a variant, because assignment is computed locally from the user id.

Do I need dif Cloud to use feature flags in React? No. Assignment works entirely locally, so flags and rollouts run with nothing hosted. Add a publishableKey only when you want to send exposure and outcome events to dif Cloud for analysis.