Features
- Connects seamlessly with your GradualRollout backend API.
- Framework agnostic: Use with React, Angular, Vue, or vanilla JS.
- Consistent user bucketing via hashing for percentage rollouts.
- Event-driven API with
flagsUpdated
,error
, andinitialized
events. - Automatic polling with configurable interval and manual refresh support.
- Simple to initialize and use with TypeScript support.
- Dynamic user identity support: Works with anonymous users (
anonId
) before login and updates seamlessly post-login (userId
).
Installation
Install the SDK using npm or yarn:
npm install gradual-rollout-sdk
# or
yarn add gradual-rollout-sdk
Basic Usage
Initialize the SDK and check feature flags:
import { GradualRolloutSDK } from 'gradual-rollout-sdk';
const sdk = new GradualRolloutSDK({
apiKey: 'YOUR_API_KEY', // Required
userId: 'user_123', // Optional: for logged-in users
anonId: 'some_uuid', // Optional: for anonymous users
pollingIntervalMs: 30000 // Optional: defaults to 60s
});
// Listen for SDK events
sdk.on('initialized', () => console.log('SDK ready!'));
sdk.on('flagsUpdated', (flags) => console.log('Flags:', flags));
sdk.on('error', (err) => console.error('SDK error:', err);
// Initialize the SDK and fetch flags
await sdk.init();
// Check if a feature is enabled
if (sdk.isFeatureEnabled('new-dashboard-ui')) {
// Render new UI
}
// Manually refresh flags
await sdk.refreshFlags();
// Clean up SDK resources when no longer needed
sdk.destroy();
React Integration
For React applications, use a context provider and a custom hook for seamless integration.
1. Create your GradualRolloutProvider.tsx
:
// GradualRolloutProvider.tsx
import React, { createContext, useContext, useEffect, useMemo, useState, useCallback } from 'react';
import { GradualRolloutSDK, FeatureFlag } from 'gradual-rollout-sdk';
import { v4 as uuidv4 } from 'uuid'; // npm install uuid
interface GradualRolloutContextType {
isFeatureEnabled: (flagKey: string) => boolean;
initialized: boolean;
refreshFlags: () => Promise<void>;
setIdentity: (userId?: string, anonId?: string) => Promise<void>;
}
const GradualRolloutContext = createContext<GradualRolloutContextType | undefined>(undefined);
function useAnonId() {
const [anonId, setAnonId] = useState<string | null>(null);
useEffect(() => {
let storedAnonId = localStorage.getItem('gradual_rollout_anon_id'); // Unique key
if (!storedAnonId) {
storedAnonId = uuidv4();
localStorage.setItem('gradual_rollout_anon_id', storedAnonId);
}
setAnonId(storedAnonId);
}, []);
return anonId;
}
export const GradualRolloutProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const anonId = useAnonId();
const [sdkInstance, setSdkInstance] = useState<GradualRolloutSDK | null>(null);
const [initialized, setInitialized] = useState(false);
useEffect(() => {
if (!anonId) return;
const sdk = new GradualRolloutSDK({
apiKey: 'YOUR_API_KEY', // <<< IMPORTANT: Replace with your actual API key
anonId,
pollingIntervalMs: 6000,
});
setSdkInstance(sdk);
sdk.on('initialized', () => setInitialized(true));
sdk.init();
return () => {
sdk.destroy();
};
}, [anonId]);
const isFeatureEnabled = useCallback(
(flagKey: string) => (sdkInstance ? sdkInstance.isFeatureEnabled(flagKey) : false),
[sdkInstance]
);
const refreshFlags = useCallback(async () => {
await sdkInstance?.refreshFlags();
}, [sdkInstance]);
const setIdentity = useCallback(async (userId?: string, anonId?: string) => {
await sdkInstance?.setIdentity(userId, anonId);
}, [sdkInstance]);
const value = useMemo(
() => ({ isFeatureEnabled, initialized, refreshFlags, setIdentity }),
[isFeatureEnabled, initialized, refreshFlags, setIdentity]
);
return (
<GradualRolloutContext.Provider value={value}>
{children}
</GradualRolloutContext.Provider>
);
};
export function useGradualRollout() {
const context = useContext(GradualRolloutContext);
if (context === undefined) { // Added parenthesis for correct syntax
throw new Error('useGradualRollout must be used within a GradualRolloutProvider');
}
return context;
}
2. Wrap your application with the provider (e.g., in App.tsx
or Next.js's layout.tsx
):
// App.tsx or layout.tsx
import { GradualRolloutProvider } from './GradualRolloutProvider'; // Adjust path as needed
function AppOrLayout({ children }: { children: React.ReactNode }) { // Added children prop type
return (
<GradualRolloutProvider>
{/* Your application components */}
{children} {/* Render children */}
</GradualRolloutProvider>
);
}
export default AppOrLayout;
3. Use the useGradualRollout
hook in your components:
// MyFeatureComponent.tsx
import { useGradualRollout } from './GradualRolloutProvider'; // Adjust path as needed
function MyFeatureComponent() {
const { isFeatureEnabled, initialized, refreshFlags, setIdentity } = useGradualRollout();
if (!initialized) {
return <div className="text-gray-500">Loading features...</div>;
}
return (
<div>
{isFeatureEnabled('my-new-feature') ? (
<p className="text-green-400">New feature is enabled!</p>
) : (
<p className="text-red-400">New feature is not enabled.</p>
)}
<button
onClick={refreshFlags}
className="mt-4 bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700 transition-colors"
>
Refresh Flags
</button>
<button
onClick={() => setIdentity('user-john-doe')}
className="mt-4 ml-2 bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700 transition-colors"
>
Set User ID
</button>
</div>
);
}
API Reference
Constructor Options
Option | Type | Required | Description |
---|---|---|---|
apiKey | string | Yes | Your project's API key for authentication. |
userId | string | Cond. | Unique identifier for a logged-in user. |
anonId | string | Cond. | Unique identifier for an anonymous visitor. Usually stored in local storage. |
pollingIntervalMs | number | No | Interval in milliseconds for automatic flag polling (default: 60000ms). |
apiBaseUrl | string | No | Base URL for the GradualRollout backend API (default: SDK endpoint). |
Methods
init(): Promise<void>
– Initializes the SDK, fetches flags, and starts polling if configured. Must be called once.isFeatureEnabled(flagKey: string): boolean
– Checks if a specific feature flag is enabled for the current user.refreshFlags(): Promise<void>
– Manually fetches the latest flags from the backend.setIdentity(userId?: string, anonId?: string): Promise<void>
– Dynamically updates the user identity and refreshes flags. Call this on user login/logout.on(event: string, handler: Function)
– Subscribes to SDK events. See Events section below.off(event: string, handler: Function)
– Unsubscribes a handler from an event.destroy(): void
– Cleans up all SDK resources, including polling timers and event listeners. Call this on application unload or component unmount.
Events
The SDK emits the following events you can subscribe to:
Event Name | Payload Type | Description |
---|---|---|
flagsUpdated | FeatureFlag[] | Fired when feature flags are successfully fetched or updated. |
error | Error | Fired if an API request fails or an internal SDK error occurs. |
initialized | void | Fired once the SDK has successfully completed its initial flag fetch and is ready. |
Dynamic User Identity Support
- Achieve seamless A/B testing before and after user login without losing rollout consistency.
- Anonymous user bucketing using an automatically managed
anonId
. - Smooth transition to a logged-in
userId
after authentication, preserving feature experiences.
Why this is a Big Improvement
- Stateless and Flexible: Unlike traditional systems that often require manual user assignments or backend configuration updates for phased rollouts, our SDK uses stateless, hash-based bucketing.
- Runtime Updates: Allows for dynamic identity updates and flag refreshes at runtime, eliminating the need for app reloads or complex deployments.