Feature flags in React
A feature flag lets you ship a React component now and decide later who sees it. This guide adds flags to a React app with one small hook, and covers the parts that matter in React: signed-in users, avoiding a flash of the wrong UI, and server rendering.
The examples use Flagbearer's browser SDK. The React docs have the full reference.
Install
npm install @flagbearer/webOne client and a useFlag hook
Create the client once, in its own module, and read flags with React's
useSyncExternalStore. That way a component re-renders when a flag changes:
// src/flags.ts
import { useEffect, useSyncExternalStore } from "react";
import { init, type FlagKey } from "@flagbearer/web";
export const flags = init({ clientKey: "fbc_..." });
export function useFlag(key: FlagKey, defaultValue: boolean) {
const enabled = useSyncExternalStore(
flags.subscribe,
() => flags.getSnapshot()[key] ?? defaultValue,
() => defaultValue,
);
useEffect(() => {
flags.isEnabled(key, defaultValue);
}, [key, defaultValue, enabled]);
return enabled;
}Check a flag in a component
function CheckoutPage() {
const newCheckout = useFlag("new-checkout", false);
return newCheckout ? <NewCheckout /> : <Checkout />;
}The second argument is the default: what the component shows until flags have loaded. When you turn the flag on in Flagbearer, the component updates within seconds, with no redeploy and no page reload.
Tell the client who is signed in
Rollouts work per user, so tell the client who's signed in as soon as you know, before React renders anything for them:
async function loadSession() {
const user = await fetchCurrentUser();
if (user) flags.identify({ key: user.id, email: user.email });
return user;
}The email is optional. Include it to turn flags on for your testers by email.
No flash of the wrong UI
Some screens shouldn't switch from the old version to the new one while the user is looking. Add a second hook that says whether flags have loaded, and hold those screens back until it's true:
// src/flags.ts
export function useFlagsLoaded() {
return useSyncExternalStore(
flags.subscribe,
() => flags.getStatus() !== "loading",
() => false,
);
}const loaded = useFlagsLoaded();
const newCheckout = useFlag("new-checkout", false);
if (!loaded) return <Spinner />;
return newCheckout ? <NewCheckout /> : <Checkout />;Server rendering
During server rendering and hydration, useFlag returns the default and
useFlagsLoaded returns false, so the server and the browser agree and
there are no hydration mismatches. The real values take over once flags load
in the browser.
Typed flag keys
In TypeScript, one command writes a file listing your app's flags. After that, a misspelled key is a type error and your editor autocompletes them:
npx @flagbearer/cli generate types --app <app> --out src/flagbearer.d.tsNext: roll it out
With the flag in place, the feature can go to your team first and then to everyone, a step at a time. See how to roll a feature out gradually.