Skip to main content

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();
};
}
APIPurpose
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().

Security

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​

  1. With cache: Uses cached flags from storage
  2. Without cache: Uses featureDefaults
  3. 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: Pauses refresh interval
  • Resume: Resumes and refreshes

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.getFlags(), 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 SDKReact Native SDK
createTogglyProvider()<TogglyProvider> component
FeatureFeature (same)
useTogglyuseToggly (enhanced)
-useFeatureFlag (new)
-useFeatureGate (new)
-Storage adapters (new)

Migration Steps​

  1. Install React Native SDK packages
  2. Replace createTogglyProvider() with <TogglyProvider>
  3. Add storage adapter for persistent caching
  4. Update imports to use new package names
  5. Optionally use new hooks like useFeatureFlag