Advanced Usage
Advanced features and patterns for the React Native SDK.
Extensibility Hooks
Integrate with analytics, monitoring, or implement custom behaviors using hooks.
Creating a Hook
import { Hook } from '@ops-ai/toggly-hooks-types';
const analyticsHook: Hook = {
getMetadata: () => ({
name: 'Analytics',
version: '1.0.0',
}),
beforeEvaluation: async (data) => {
// Called before feature evaluation
console.log('Evaluating:', data.featureKey);
},
afterEvaluation: async (data) => {
// Called after evaluation
analytics.track('Feature Evaluated', {
feature: data.featureKey,
enabled: data.result,
userId: data.userId,
});
},
beforeIdentify: async (data) => {
// Called before identity change
console.log('Changing identity from:', data.previousUserId);
},
afterIdentify: async (data) => {
// Called after identity change
analytics.identify(data.userId);
},
afterRefresh: async () => {
// Called after flags refresh
console.log('Flags refreshed');
},
};
Registering Hooks
At initialization:
<TogglyProvider
appKey="your-app-key"
environment="production"
hooks={[analyticsHook, loggingHook]}
>
<App />
</TogglyProvider>
At runtime:
function App() {
const { toggly } = useToggly();
useEffect(() => {
toggly.addHook(analyticsHook);
return () => {
toggly.removeHook(analyticsHook);
};
}, [toggly]);
return <Content />;
}
Hook Execution Order
- before hooks: Execute in FIFO order (first registered, first executed)
- after hooks: Execute in LIFO order (last registered, first executed)
This creates a "wrap" pattern similar to middleware.
Common Hook Use Cases
Firebase Analytics:
const firebaseHook: Hook = {
getMetadata: () => ({ name: 'Firebase', version: '1.0.0' }),
afterEvaluation: async (data) => {
await analytics().logEvent('feature_flag_evaluated', {
feature_key: data.featureKey,
enabled: data.result,
});
},
};
Debug Logging:
const debugHook: Hook = {
getMetadata: () => ({ name: 'Debug', version: '1.0.0' }),
afterEvaluation: async (data) => {
if (__DEV__) {
console.log(`[Toggly] ${data.featureKey} = ${data.result}`);
}
},
};
Device-local post-filter gates
Gate bundles of flags behind device-local master switches (AsyncStorage, Settings,
debug menus) while worker rollouts stay on Toggly. Effective value is
remote AND local at read time — the cached remote map is never mutated.
See the full guide: Post-filter gates.
Register at provider init
import type { LocalGate } from '@ops-ai/react-native-toggly-core';
let apiRedesignEnabled = false;
const localGates: LocalGate[] = [{
id: 'apiRedesign',
flagKeys: ['ApiV2Checkout', 'ApiV2Profile'],
isEnabled: () => apiRedesignEnabled,
}];
<TogglyProvider
appKey="your-app-key"
environment="Production"
localGates={localGates}
>
<App />
</TogglyProvider>
Update at runtime
function SettingsScreen() {
const { toggly } = useTogglyContext();
const turnOff = () => {
apiRedesignEnabled = false;
toggly.setLocalGates([{
id: 'apiRedesign',
flagKeys: ['ApiV2Checkout', 'ApiV2Profile'],
isEnabled: () => apiRedesignEnabled,
}]);
// Instant — no network; hooks and <Feature> re-read effective flags
toggly.notifyLocalGatesChanged();
};
const turnOn = async () => {
apiRedesignEnabled = true;
await toggly.refresh(); // pick up remote rollouts for newly enabled gate
toggly.notifyLocalGatesChanged();
};
}
| API | Purpose |
|---|---|
localGates (provider / config) | Initial gate registry |
setLocalGates(gates) | Replace the registry at runtime |
notifyLocalGatesChanged() | Recompute effective flags locally; emit UI updates (no network) |
useFeatureFlag, useFeatureGate, and <Feature> subscribe to
effectiveFlagsChanged, so they re-render when you call
notifyLocalGatesChanged().
Post-filter gates are for UX and product gating on the device. Enforce security-sensitive behavior on your server as well.
Offline Support
The SDK handles offline scenarios gracefully.
Configuration
<TogglyProvider
appKey="your-app-key"
environment="production"
storage={createAsyncStorageAdapter()}
featureDefaults={{
'critical-feature': true, // Fallback when offline
'beta-feature': false,
}}
>
<App />
</TogglyProvider>
Offline Behavior
- With cache: Uses cached flags from storage
- Without cache: Uses
featureDefaults - On reconnect: Automatically refreshes flags
Network State Integration
import NetInfo from '@react-native-community/netinfo';
function App() {
const { refresh } = useToggly();
const [isConnected, setIsConnected] = useState(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
const wasDisconnected = !isConnected;
setIsConnected(state.isConnected ?? false);
// Refresh when coming back online
if (state.isConnected && wasDisconnected) {
refresh();
}
});
return () => unsubscribe();
}, [isConnected, refresh]);
return <Content />;
}
App Lifecycle
The SDK automatically handles app lifecycle:
- Foreground: Triggers flag refresh
- Background / inactive: Pauses refresh interval and starts a bounded telemetry flush
- Resume: Resumes and refreshes
The provider uses the same Core reporter across these transitions. Final
unmount disposes it synchronously and starts at most one final envelope. Await
useToggly().flushTelemetry() before a planned teardown when you need to wait
for the current best-effort send attempt.
Custom Lifecycle Handling
import { AppState } from 'react-native';
function App() {
const { refresh } = useToggly();
useEffect(() => {
const subscription = AppState.addEventListener('change', (state) => {
if (state === 'active') {
// App came to foreground
refresh();
}
});
return () => subscription.remove();
}, [refresh]);
return <Content />;
}
Error Handling
Provider-Level Errors
<TogglyProvider
appKey="your-app-key"
environment="production"
onError={(error) => {
// Log to crash reporting
Sentry.captureException(error);
// Show user-friendly message if critical
if (error.message.includes('network')) {
Alert.alert('Offline', 'Some features may be unavailable');
}
}}
>
<App />
</TogglyProvider>
Hook-Level Errors
function MyComponent() {
const { isEnabled, error } = useFeatureFlag('my-feature');
if (error) {
// Handle gracefully
console.error('Feature flag error:', error);
return <FallbackUI />;
}
return isEnabled ? <NewFeature /> : <OldFeature />;
}
Performance Optimization
Memoization
import { useMemo } from 'react';
function Dashboard() {
const { isFeatureOn } = useToggly();
// Memoize feature checks that don't change often
const features = useMemo(() => ({
hasNewDashboard: isFeatureOn('new-dashboard'),
hasPremium: isFeatureOn('premium-features'),
hasAnalytics: isFeatureOn('analytics'),
}), [isFeatureOn]);
return <DashboardContent features={features} />;
}
Avoid Re-renders
// Bad: Creates new object every render
<Feature featureKeys={['a', 'b']}>
// Good: Stable reference
const FEATURE_KEYS = ['a', 'b'];
<Feature featureKeys={FEATURE_KEYS}>
Testing
Mocking in Tests
import { TogglyProvider } from '@ops-ai/react-native-toggly';
const mockFlags = {
'feature-a': true,
'feature-b': false,
};
function renderWithToggly(component) {
return render(
<TogglyProvider featureDefaults={mockFlags}>
{component}
</TogglyProvider>
);
}
test('shows new feature when enabled', () => {
const { getByText } = renderWithToggly(<MyComponent />);
expect(getByText('New Feature')).toBeTruthy();
});
Testing Different Flag States
test('shows old feature when new feature is disabled', () => {
const { getByText } = render(
<TogglyProvider featureDefaults={{ 'new-feature': false }}>
<MyComponent />
</TogglyProvider>
);
expect(getByText('Old Feature')).toBeTruthy();
});
Debugging
Debug Information
function DebugScreen() {
const { toggly, isReady, isLoading, error } = useToggly();
return (
<View>
<Text>Ready: {isReady ? 'Yes' : 'No'}</Text>
<Text>Loading: {isLoading ? 'Yes' : 'No'}</Text>
<Text>Error: {error?.message || 'None'}</Text>
{/* Access internal state if needed */}
<Text>Flags: {JSON.stringify(toggly.currentFeatures, null, 2)}</Text>
</View>
);
}
Development Mode Logging
const devHook: Hook = {
getMetadata: () => ({ name: 'DevLogger', version: '1.0.0' }),
afterEvaluation: async (data) => {
if (__DEV__) {
console.log(
`%c[Toggly] ${data.featureKey}: ${data.result}`,
`color: ${data.result ? 'green' : 'red'}`
);
}
},
};
Migration Guide
From React SDK
The React Native SDK has a similar but enhanced API:
| React SDK | React Native SDK |
|---|---|
createTogglyProvider() | <TogglyProvider> component |
Feature | Feature (same) |
useToggly | useToggly (enhanced) |
| - | useFeatureFlag (new) |
| - | useFeatureGate (new) |
| - | Storage adapters (new) |
Migration Steps
- Install React Native SDK packages
- Replace
createTogglyProvider()with<TogglyProvider> - Add storage adapter for persistent caching
- Update imports to use new package names
- Optionally use new hooks like
useFeatureFlag