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
Examples
Install
First, let's install the required packages! The React SDK requires @basestack/flags-react for React-specific functionality.
npm install @basestack/flags-reactpnpm install @basestack/flags-reactyarn add @basestack/flags-reactbun add @basestack/flags-reactEnvironment 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=
# 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.
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.
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 totrueif no initial flags are provided, so flags are fetched automaticallyconfig: 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.
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
refreshfunction 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:
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:
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:
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:
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 lifecycleuseFeatureFlagModals()— Imperatively open preview or feedback modalsuseFlag().openFeedbackModal()— Flag-local feedback flow using the current flag sluguseFeatureFlagModalsOptional()— Safe access when the modal provider may be absent
Create Web Components configuration
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.
"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).
"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.
"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
FeatureFlagModalsProvideris not mounted,openFeedbackModalis a safe no-op.
Hooks reference
useFeatureFlagModals()
| Property | Type | Description |
|---|---|---|
ready | boolean | Whether web components are registered and mounted |
error | Error | null | Registration or setup error |
openPreviewModal | () => void | Opens the preview modal |
openFeedbackModal | (flagKey: string, options?: { featureName?: string; metadata?: Record<string, unknown> }) => void | Opens 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
| Property | Type | Description |
|---|---|---|
openFeedbackModal | (options?: { featureName?: string; metadata?: Record<string, unknown> }) => void | Opens feedback modal for the current flag |
enabled | boolean | Includes 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
| Issue | Solution |
|---|---|
useFeatureFlagModals must be used within FeatureFlagModalsProvider | Mount FeatureFlagModalsProvider higher in the component tree |
FlagsProvider is missing in the component tree | Ensure FeatureFlagModalsProvider is inside FlagsProvider |
Buttons stay disabled (ready === false) | Check error, verify package install, and inspect the console |
| Preview toggle not reflected | useFlag reads from bs-flags-preview-state in localStorage; clear storage for a clean test |
| Wrong API target | Override 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 } | undefined3. 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 error5. 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:
- Check Environment Variables: Ensure your environment variables are correctly prefixed (
VITE_for Vite,REACT_APP_for CRA) - Verify Provider Setup: Make sure
FlagsProviderwraps your entire app - Check Network: Verify your application can reach the Basestack API endpoint
- 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:
- Install Types: Ensure
@basestack/flags-reactare installed - Check Imports: Use the correct import paths (
@basestack/flags-react/clientfor hooks) - Type Payloads: Use TypeScript generics to type your payloads
Environment Variables Not Available
If environment variables aren't available:
- Check Prefix: Use the correct prefix for your build tool
- Restart Dev Server: After adding environment variables, restart your dev server
- Verify
.envFile: Ensure your.envfile is in the project root - Check Build Tool Config: Some build tools require additional configuration
Flags Not Updating
If flags aren't updating:
- Check Cache: Flags are cached for 5 minutes by default. Wait for cache to expire or clear it programmatically
- Use Refresh: Call the
refresh()function returned byuseFlagto manually refetch - Check Flag State: Verify the flag state in your Basestack dashboard
API Reference - Props & Parameters
Components
FlagsProvider
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
config | SDKConfig | Yes | - | Configuration object for the Flags SDK |
children | ReactNode | Yes | - | React children to be wrapped by the provider |
initialFlags | Flag[] | No | [] | Initial flags to populate the provider with |
preload | boolean | No | true | Whether to automatically preload flags on mount |
onError | (error: Error) => void | No | - | Callback function called when an error occurs |
Hooks
useFlag<TPayload>(slug, options?)
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slug | string | Yes | - | The flag slug to retrieve |
options | UseFlagOptions<TPayload> | No | - | Options object (see below) |
Options (UseFlagOptions):
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
defaultEnabled | boolean | No | false | Default enabled state if flag is not loaded |
defaultPayload | TPayload | No | - | Default payload value if flag is not loaded |
fetch | boolean | No | true | Whether to automatically fetch the flag if not cached |
Returns (UseFlagResult<TPayload>):
| Property | Type | Description |
|---|---|---|
flag | Flag | undefined | The flag object if available |
enabled | boolean | Whether the flag is enabled |
payload | TPayload | undefined | The flag payload |
isLoading | boolean | Whether the flag is currently loading |
error | Error | null | Any error that occurred during fetching |
refresh | () => Promise<Flag | undefined> | Function to manually refresh the flag |
useFlags()
Parameters: None
Returns:
| Property | Type | Description |
|---|---|---|
flags | Flag[] | Array of all flags |
flagsBySlug | Record<string, Flag> | Object mapping flag slugs to flag objects |
isLoading | boolean | Whether flags are currently loading |
error | Error | null | Any error that occurred during fetching |
refresh | () => Promise<void> | Function to manually refresh all flags |
useFlagsClient()
Parameters: None
Returns:
| Property | Type | Description |
|---|---|---|
| (client) | FlagsSDK | The Flags SDK client instance |
useFlagsContext()
Parameters: None
Returns:
| Property | Type | Description |
|---|---|---|
client | FlagsSDK | The Flags SDK client instance |
flags | Map<string, Flag> | Map of flags by slug |
loading | boolean | Whether flags are currently loading |
error | Error | null | Any error that occurred |
refresh | () => Promise<void> | Function to manually refresh all flags |
upsertFlag | (flag: Flag) => void | Function to update or insert a flag |
Server Functions
fetchFlags(config)
| Parameter | Type | Required | Description |
|---|---|---|---|
config | SDKConfig | Yes | Configuration 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