Basestack Docs
Frontend

React (Official SDK)

Seamless integration with React streamlines the testing and development of features across production and various environments. The Basestack React SDK provides React hooks and components that make it easy to use feature flags in any React application, whether you're using Vite, Create React App, or a custom React setup.

The SDK leverages React Context to provide feature flags throughout your component tree, eliminating the need to pass props manually. It includes built-in caching, loading states, and error handling to ensure a smooth developer experience.

Looking for Next.js integration? Check out the Next.js SDK instead.

Getting started

We only support ESM (ECMAScript Modules) in the browser or server environment. If you are using CommonJS, you will need to use a bundler like Webpack or Rollup to bundle your code.

Examples

Install

First, let's install the required packages! The React SDK requires @basestack/flags-react for React-specific functionality.

Install the SDK
npm install @basestack/flags-react
pnpm install @basestack/flags-react
yarn add @basestack/flags-react
bun add @basestack/flags-react

Environment Variables

Configure your environment variables to connect to your Basestack Feature Flags project. The prefix depends on your build tool:

  • Vite: Use VITE_ prefix
  • Create React App: Use REACT_APP_ prefix
  • Custom setup: Use any prefix your bundler supports

When it comes to environment variables, pay attention to the framework you're using. For example, NEXT_PUBLIC_ is specific to Next.js, while in Vite.js, it would be VITE_. Example VITE_FEATURE_FLAGS_BASE_URL= or Node.js FEATURE_FLAGS_BASE_URL=

.env
# BASESTACK FEATURE FLAGS
VITE_FEATURE_FLAGS_BASE_URL="https://flags.basestack.co/api/v1"
VITE_FEATURE_FLAGS_PROJECT_KEY=""
VITE_FEATURE_FLAGS_ENVIRONMENT_KEY=""

You can find your project and environment keys in your Basestack Feature Flags Dashboard.

Make sure to add your .env file to .gitignore to keep your keys secure. Never commit sensitive credentials to version control.

Create Configuration File

Create a configuration file to centralize your SDK settings. This makes it easy to reuse the configuration throughout your application and update it in one place.

src/flagsConfig.ts
import type { SDKConfig } from "@basestack/flags-react";

// For Vite projects
export const flagsConfig: SDKConfig = {
  baseURL: import.meta.env.VITE_FEATURE_FLAGS_BASE_URL,
  projectKey: import.meta.env.VITE_FEATURE_FLAGS_PROJECT_KEY,
  environmentKey: import.meta.env.VITE_FEATURE_FLAGS_ENVIRONMENT_KEY,
};

// For Create React App, use this instead:
// export const flagsConfig: SDKConfig = {
//   baseURL: process.env.REACT_APP_FEATURE_FLAGS_BASE_URL,
//   projectKey: process.env.REACT_APP_FEATURE_FLAGS_PROJECT_KEY,
//   environmentKey: process.env.REACT_APP_FEATURE_FLAGS_ENVIRONMENT_KEY,
// };

Setup the Provider

The FlagsProvider is a React Context Provider that wraps your application and provides feature flags to all child components. It manages the SDK client instance, caches flags, and handles loading states.

In your application entry point (typically main.tsx or index.tsx), wrap your app with the FlagsProvider. For better performance, you can optionally preload flags before rendering your app.

src/main.tsx
import type { Flag } from "@basestack/flags-react";
import { FlagsProvider } from "@basestack/flags-react/client";
import { fetchFlags } from "@basestack/flags-react/server";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
import { flagsConfig } from "./flagsConfig";

async function bootstrap() {
  const container = document.getElementById("root");

  if (!container) {
    throw new Error("Root container #root was not found.");
  }

  // Optionally preload flags before rendering
  // This reduces loading states and improves initial render performance
  let initialFlags: Flag[] = [];

  try {
    initialFlags = await fetchFlags(flagsConfig);
  } catch (error) {
    console.error("Failed to preload flags", error);
    // App will still work, flags will be fetched on-demand
  }

  const root = createRoot(container);

  root.render(
    <StrictMode>
      <FlagsProvider
        config={flagsConfig}
        initialFlags={initialFlags}
        preload={initialFlags.length === 0}
      >
        <App />
      </FlagsProvider>
    </StrictMode>,
  );
}

void bootstrap();

Key points:

  • initialFlags: Preloaded flags from server-side fetch (optional but recommended)
  • preload: Set to true if no initial flags are provided, so flags are fetched automatically
  • config: Your SDK configuration object

Use Flags in Components

Now you can use the useFlag hook in any component within your provider tree. The hook returns the flag's enabled state, payload, loading status, and a refresh function.

src/App.tsx
import { useFlag } from "@basestack/flags-react/client";

export function App() {
  const { enabled, payload, isLoading, refresh } = useFlag<{
    color?: string;
  }>("header");

  if (isLoading) {
    return <div>Loading feature flags...</div>;
  }

  return (
    <section style={{ fontFamily: "sans-serif", padding: "2rem" }}>
      <h1>React + Vite Example</h1>
      <p>
        Flag <code>header</code> is{" "}
        {enabled ? "enabled" : "disabled"}
      </p>
      {enabled && payload ? (
        <p>Payload: {JSON.stringify(payload, null, 2)}</p>
      ) : null}
      <button type="button" onClick={() => refresh()}>
        Refresh flag
      </button>
    </section>
  );
}

The useFlag hook:

  • Automatically fetches the flag if it's not in cache
  • Returns loading state while fetching
  • Provides a refresh function to manually refetch the flag
  • Supports TypeScript generics for type-safe payloads

Advanced Usage

Using Multiple Flags

You can use multiple useFlag hooks in the same component, or use useFlags to get all flags at once:

src/components/FeatureShowcase.tsx
import { useFlag, useFlags } from "@basestack/flags-react/client";

export function FeatureShowcase() {
  const headerFlag = useFlag<{ variant: string }>("header");
  const footerFlag = useFlag("footer");
  const { flags, isLoading } = useFlags();

  return (
    <div>
      <h2>Individual Flags</h2>
      <p>Header: {headerFlag.enabled ? "Enabled" : "Disabled"}</p>
      <p>Footer: {footerFlag.enabled ? "Enabled" : "Disabled"}</p>

      <h2>All Flags</h2>
      {isLoading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {flags.map((flag) => (
            <li key={flag.slug}>
              {flag.slug}: {flag.enabled ? "enabled" : "disabled"}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Conditional Rendering

Use flags to conditionally render features:

src/components/ConditionalFeature.tsx
import { useFlag } from "@basestack/flags-react/client";

export function ConditionalFeature() {
  const { enabled, payload } = useFlag<{ theme: string }>("new-ui");

  if (!enabled) {
    return <OldUI />;
  }

  return <NewUI theme={payload?.theme} />;
}

Accessing the SDK Client Directly

For advanced use cases, you can access the SDK client directly using useFlagsClient:

src/components/AdvancedUsage.tsx
import { useFlagsClient } from "@basestack/flags-react/client";
import { useEffect, useState } from "react";
import type { Flag } from "@basestack/flags-react";

export function AdvancedUsage() {
  const client = useFlagsClient();
  const [flag, setFlag] = useState<Flag | null>(null);

  useEffect(() => {
    async function fetchFlag() {
      const result = await client.getFlag("header");
      setFlag(result);
    }
    fetchFlag();
  }, [client]);

  return flag ? <div>Flag: {flag.slug}</div> : <div>Loading...</div>;
}

Error Handling

Always handle potential errors when working with flags:

src/components/ErrorHandling.tsx
import { useFlag } from "@basestack/flags-react/client";

export function ErrorHandling() {
  const { enabled, error, isLoading } = useFlag("header");

  if (error) {
    return (
      <div>
        <p>Failed to load feature flag: {error.message}</p>
        <p>Falling back to default behavior...</p>
        {/* Render default UI */}
      </div>
    );
  }

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return <div>Feature is {enabled ? "enabled" : "disabled"}</div>;
}

Web Components Integration

The React SDK includes built-in support for Basestack's feature preview and feedback modals. The FeatureFlagModalsProvider registers and mounts the underlying Web Components (<feature-flag-preview-modal /> and <feature-flag-feedback-modal />), exposing a React-friendly API via hooks.

For full attribute reference, events, API behavior, and framework-agnostic usage, see the Web Components documentation.

What you get

With the client entrypoint (@basestack/flags-react/client):

  • FeatureFlagModalsProvider — Registers and mounts the Web Components, manages lifecycle
  • useFeatureFlagModals() — Imperatively open preview or feedback modals
  • useFlag().openFeedbackModal() — Flag-local feedback flow using the current flag slug
  • useFeatureFlagModalsOptional() — Safe access when the modal provider may be absent

Create Web Components configuration

libs/feature-flags/flags-wc-config.ts
import type { FeatureFlagModalsConfig } from "@basestack/flags-react/client";

export const flagsWcConfig: FeatureFlagModalsConfig = {
  preview: {
    theme: "light",
    heading: "Feature Preview",
    subtitle: "Select and enable previews that fit your workflow.",
    selectionPrompt: "Choose a preview to view",
    selectionPlaceholder: "Select a feature to see details.",
    enableLabel: "Enable",
    enabledLabel: "Disable",
    loadingLabel: "Loading feature previews...",
    emptyLabel: "No feature previews are currently available.",
    previewBadgeLabel: "Preview",
    expiresSoonLabel: "Expiring soon",
    learnMoreLabel: "Learn more",
    // apiEndpoint?: defaults to `${baseURL}/flags/preview`
    // projectKey?: defaults to FlagsProvider config.projectKey
    // environmentKey?: defaults to FlagsProvider config.environmentKey
  },
  feedback: {
    theme: "light",
    heading: "Feedback",
    moodPrompt: "How did this feature make you feel?",
    ratingPrompt: "How would you rate this feature?",
    feedbackLabel: "Your feedback",
    feedbackPlaceholder: "Share your thoughts...",
    submitLabel: "Submit Feedback",
    privacyPolicyUrl: "https://basestack.co/legal/privacy",
    privacyPolicyLabel:
      "Please avoid adding sensitive/personal information.",
    privacyPolicyLinkLabel: "More details",
    // apiEndpoint?: defaults to `${baseURL}/flags/preview/feedback`
    // projectKey/environmentKey also fallback to provider values
  },
};

Setup the providers

FeatureFlagModalsProvider must be inside FlagsProvider. The provider automatically calls registerFeatureFlagComponents(), mounts both modals, and exposes controls via context.

app/Providers.tsx
"use client";

import type { ReactNode } from "react";
import type { Flag } from "@basestack/flags-react";
import {
  FlagsProvider,
  FeatureFlagModalsProvider,
} from "@basestack/flags-react/client";
import { flagsConfig } from "@/libs/feature-flags/flags-config";
import { flagsWcConfig } from "@/libs/feature-flags/flags-wc-config";

export function Providers({
  children,
  initialFlags,
}: {
  children: ReactNode;
  initialFlags?: Flag[];
}) {
  return (
    <FlagsProvider
      config={flagsConfig}
      initialFlags={initialFlags}
      preload={!initialFlags?.length}
    >
      <FeatureFlagModalsProvider
        config={flagsWcConfig}
        onError={(err) => console.error("[FeatureFlagModals]", err)}
      >
        {children}
      </FeatureFlagModalsProvider>
    </FlagsProvider>
  );
}

Open preview and feedback modals

Use useFeatureFlagModals() when you need direct modal controls (e.g., from a settings panel or global trigger).

src/components/FeatureControls.tsx
"use client";

import { useFeatureFlagModals } from "@basestack/flags-react/client";

export function FeatureControls() {
  const { ready, error, openPreviewModal, openFeedbackModal } =
    useFeatureFlagModals();

  if (error) return <p>Modals unavailable: {error.message}</p>;

  return (
    <>
      <button onClick={openPreviewModal} disabled={!ready}>
        Open feature preview modal
      </button>

      <button
        onClick={() =>
          openFeedbackModal("command-palette", {
            featureName: "Command Palette", 
            metadata: {
              userId: "usr_0012",
              subscription: "lite",
              openedFrom: "home-page-command-palette",
              context: { locale: "en-US" },
            },
          })
        }
        disabled={!ready}
      >
        Leave feedback
      </button>
    </>
  );
}

metadata is an optional object that can be used to attach any JSON-serializable data to feedback submissions (e.g. userId, subscription, context). This data is sent with the feedback payload to the API. It is not displayed in the feedback modal.

featureName: is a human-friendly name for the feature. It can be anything, even content from your internationalization tool or library.

Flag-local feedback via useFlag()

useFlag() returns openFeedbackModal(options?) for opening the feedback modal tied to the current flag slug.

src/components/HeaderFeature.tsx
"use client";

import { useFlag } from "@basestack/flags-react/client";

export function HeaderFeature() {
  const { enabled, payload, isLoading, openFeedbackModal } = useFlag<{
    variant?: string;
  }>("header");

  if (isLoading) return <p>Checking feature flags...</p>;

  return (
    <section>
      <p>Header is {enabled ? "enabled" : "disabled"}</p>
      <pre>{JSON.stringify(payload, null, 2)}</pre>

      {enabled && (
        <button
          onClick={() =>
            openFeedbackModal({
              featureName: "Header",
              metadata: {
                userId: "usr_0012",
                subscription: "lite",
                openedFrom: "home-page-command-palette",
                context: { locale: "en-US" },
              },
            })
          }
        >
          Leave feedback for this feature
        </button>
      )}
    </section>
  );
}

Key points:

  • The flag slug is passed automatically from the hook ("header" in this example).
  • If FeatureFlagModalsProvider is not mounted, openFeedbackModal is a safe no-op.

Hooks reference

useFeatureFlagModals()

PropertyTypeDescription
readybooleanWhether web components are registered and mounted
errorError | nullRegistration or setup error
openPreviewModal() => voidOpens the preview modal
openFeedbackModal(flagKey: string, options?: { featureName?: string; metadata?: Record<string, unknown> }) => voidOpens the feedback modal for a given flag

Use when the modal provider is guaranteed to exist in the tree.

useFlag(slug, options) — Web Components–related

PropertyTypeDescription
openFeedbackModal(options?: { featureName?: string; metadata?: Record<string, unknown> }) => voidOpens feedback modal for the current flag
enabledbooleanIncludes preview overrides from localStorage (bs-flags-preview-state)
refresh() => Promise<Flag | undefined>Refetches the flag

useFeatureFlagModalsOptional()

Returns the modal API or null. Use when building components that may run with or without FeatureFlagModalsProvider.

Troubleshooting

IssueSolution
useFeatureFlagModals must be used within FeatureFlagModalsProviderMount FeatureFlagModalsProvider higher in the component tree
FlagsProvider is missing in the component treeEnsure FeatureFlagModalsProvider is inside FlagsProvider
Buttons stay disabled (ready === false)Check error, verify package install, and inspect the console
Preview toggle not reflecteduseFlag reads from bs-flags-preview-state in localStorage; clear storage for a clean test
Wrong API targetOverride preview.apiEndpoint or feedback.apiEndpoint in the modal config

For attributes, events, and API behavior of the underlying Web Components, see the Web Components documentation.

Best Practices

1. Preload Flags on App Start

Preloading flags before rendering your app eliminates loading states and improves user experience:

// In main.tsx (Pass to FlagsProvider)
// In case of network error, and API is down, the app will still work with an empty array of flags. (Use the fallback option to provide a default value)
// For more control check the provided Hooks documentation.
const initialFlags = await fetchFlags(flagsConfig);

// With fallback and onError
const initialFlags = await fetchFlags(flagsConfig, undefined, {
  // If Basestack API is down, use this fallback 
  fallback: [
    {
      slug: "header",
      enabled: true,
      createdAt: new Date(),
      updatedAt: new Date(),
      description: "",
    },
  ],
  onError: (error) => {
    console.error("Failed to load flags", error);
  },
});

// Preload specific flags (Optional)
// This disabled automatic preloading of all flags for your project
const flags = await fetchFlags(flagsConfig, ["footer", "header"]);

2. Use TypeScript Generics for Payloads

Type your flag payloads for better type safety:

const { payload } = useFlag<{ variant: string; color: string }>("header");
// payload is now typed as { variant: string; color: string } | undefined

3. Handle Loading States

Always handle loading states for better UX:

const { enabled, isLoading } = useFlag("header");

if (isLoading) return <LoadingSpinner />;

4. Provide Fallback Values

Use default values when flags fail to load:

const { enabled, error } = useFlag("header");
const isEnabled = error ? false : enabled; // Fallback to false on error

5. Cache Considerations

The SDK includes built-in caching (5 minutes default). Flags are automatically cached after first fetch, reducing unnecessary API calls. You can configure cache settings in the SDK config:

const flagsConfig: SDKConfig = {
  // ... other config
  cache: {
    enabled: true,
    ttl: 10 * 60 * 1000, // 10 minutes
    maxSize: 50, // Max 50 cached flags
  },
};

6. Avoid Unnecessary Re-renders

The hooks are optimized to prevent unnecessary re-renders. However, if you're using flags in many components, consider using useMemo for expensive computations:

const { enabled } = useFlag("header");
const expensiveValue = useMemo(() => {
  // Expensive computation based on enabled
  return computeSomething(enabled);
}, [enabled]);

Troubleshooting

Flags Not Loading

If flags aren't loading:

  1. Check Environment Variables: Ensure your environment variables are correctly prefixed (VITE_ for Vite, REACT_APP_ for CRA)
  2. Verify Provider Setup: Make sure FlagsProvider wraps your entire app
  3. Check Network: Verify your application can reach the Basestack API endpoint
  4. Check Console: Look for error messages in the browser console

Components Rendering Twice

If your components are rendering twice, this is likely due to React.StrictMode in development. This is expected behavior and helps identify potential issues. In production, components will only render once.

TypeScript Errors

If you're getting TypeScript errors:

  1. Install Types: Ensure @basestack/flags-react are installed
  2. Check Imports: Use the correct import paths (@basestack/flags-react/client for hooks)
  3. Type Payloads: Use TypeScript generics to type your payloads

Environment Variables Not Available

If environment variables aren't available:

  1. Check Prefix: Use the correct prefix for your build tool
  2. Restart Dev Server: After adding environment variables, restart your dev server
  3. Verify .env File: Ensure your .env file is in the project root
  4. Check Build Tool Config: Some build tools require additional configuration

Flags Not Updating

If flags aren't updating:

  1. Check Cache: Flags are cached for 5 minutes by default. Wait for cache to expire or clear it programmatically
  2. Use Refresh: Call the refresh() function returned by useFlag to manually refetch
  3. Check Flag State: Verify the flag state in your Basestack dashboard

API Reference - Props & Parameters

Components

FlagsProvider

PropTypeRequiredDefaultDescription
configSDKConfigYes-Configuration object for the Flags SDK
childrenReactNodeYes-React children to be wrapped by the provider
initialFlagsFlag[]No[]Initial flags to populate the provider with
preloadbooleanNotrueWhether to automatically preload flags on mount
onError(error: Error) => voidNo-Callback function called when an error occurs

Hooks

useFlag<TPayload>(slug, options?)

Parameters:

ParameterTypeRequiredDefaultDescription
slugstringYes-The flag slug to retrieve
optionsUseFlagOptions<TPayload>No-Options object (see below)

Options (UseFlagOptions):

OptionTypeRequiredDefaultDescription
defaultEnabledbooleanNofalseDefault enabled state if flag is not loaded
defaultPayloadTPayloadNo-Default payload value if flag is not loaded
fetchbooleanNotrueWhether to automatically fetch the flag if not cached

Returns (UseFlagResult<TPayload>):

PropertyTypeDescription
flagFlag | undefinedThe flag object if available
enabledbooleanWhether the flag is enabled
payloadTPayload | undefinedThe flag payload
isLoadingbooleanWhether the flag is currently loading
errorError | nullAny error that occurred during fetching
refresh() => Promise<Flag | undefined>Function to manually refresh the flag

useFlags()

Parameters: None

Returns:

PropertyTypeDescription
flagsFlag[]Array of all flags
flagsBySlugRecord<string, Flag>Object mapping flag slugs to flag objects
isLoadingbooleanWhether flags are currently loading
errorError | nullAny error that occurred during fetching
refresh() => Promise<void>Function to manually refresh all flags

useFlagsClient()

Parameters: None

Returns:

PropertyTypeDescription
(client)FlagsSDKThe Flags SDK client instance

useFlagsContext()

Parameters: None

Returns:

PropertyTypeDescription
clientFlagsSDKThe Flags SDK client instance
flagsMap<string, Flag>Map of flags by slug
loadingbooleanWhether flags are currently loading
errorError | nullAny error that occurred
refresh() => Promise<void>Function to manually refresh all flags
upsertFlag(flag: Flag) => voidFunction to update or insert a flag

Server Functions

fetchFlags(config)

ParameterTypeRequiredDescription
configSDKConfigYesConfiguration object for the Flags SDK

Returns: Promise<Flag[]> - Array of fetched flags

Note: This function can be used in any JavaScript environment (browser or Node.js) to fetch flags before rendering your React app.

Types

SDKConfig

Exported from @basestack/flags-react - Configuration for the Flags SDK

Flag

Exported from @basestack/flags-react - Flag object type

CacheConfig

Exported from @basestack/flags-react - Cache configuration type

On this page