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 and @basestack/flags-js as the core SDK dependency.
npm install @basestack/flags-react @basestack/flags-jspnpm install @basestack/flags-react @basestack/flags-jsyarn add @basestack/flags-react @basestack/flags-jsbun add @basestack/flags-react @basestack/flags-jsEnvironment 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-js";
// 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-js";
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-js";
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>;
}Best Practices
1. Preload Flags on App Start
Preloading flags before rendering your app eliminates loading states and improves user experience:
// In main.tsx
const initialFlags = await fetchFlags(flagsConfig);
// Pass to FlagsProvider2. 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-jsand@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-js - Configuration for the Flags SDK
Flag
Exported from @basestack/flags-js - Flag object type
CacheConfig
Exported from @basestack/flags-js - Cache configuration type