@transcend-io/cm-mobile-ui-react-native
v0.9.2
Published
Transcend Consent Manager mobile UI SDK for React Native
Readme
Transcend Headless React Native SDK Guide
The Transcend Headless React Native SDK provides a headless consent management API for iOS and Android apps. Import the package as @transcend-io/cm-mobile-ui-react-native. All headless API calls are accessed through the useConsent hook inside a ConsentProvider.
Requirements: React Native 0.75+, React 18+, react-native-svg, react-native-safe-area-context
Architecture support
| React Native version | New Architecture | Old Architecture (legacy bridge) | | -------------------- | ---------------- | -------------------------------- | | 0.75 – 0.81 | Supported | Supported | | 0.82+ | Supported | Not available (RN removed Old Architecture) |
The SDK registers its native module for both TurboModules (New Architecture) and the legacy bridge (Old Architecture). No extra configuration is required beyond a normal install and a full native rebuild after adding or updating the package.
Architecture note:
ConsentProviderrenderschildrenimmediately while the native SDK and UI assets load. Gate consent APIs and trackers onisReadyfromuseConsent. The bundled consent banner/modal mounts only after translations and UI config are ready.
Installation
Install @transcend-io/cm-mobile-ui-react-native from the public npm registry like any other scoped package.
Dependencies
Add the SDK, peer packages, and Transcend dependencies to your app package.json:
{
"dependencies": {
"@react-native-async-storage/async-storage": "^2.0.0",
"@transcend-io/cm-mobile-ui-react-native": "^0.9.0",
"react-native-safe-area-context": "^5.0.0",
"react-native-svg": "^15.0.0"
}
}Then install:
yarn add @transcend-io/cm-mobile-ui-react-native @react-native-async-storage/async-storage react-native-svg react-native-safe-area-context
# or
npm install @transcend-io/cm-mobile-ui-react-native @react-native-async-storage/async-storage react-native-svg react-native-safe-area-context@react-native-async-storage/async-storage is an optional peer dependency, but required for offline CDN caching (load options, translations, and logo images). Without it, the SDK uses network-only fetches.
iOS
The native iOS Headless SDK is pulled in automatically via Swift Package Manager. CocoaPods links it into the pod target but does not embed the dynamic framework in your app, so you must add a one-time Podfile hook from the shipped setup script.
- Install pods after adding the package:
cd ios && pod install- At the top of
ios/Podfile(after the React Native / Expo requires), require the setup script:
transcend_pkg = File.dirname(
`node --print "require.resolve('@transcend-io/cm-mobile-ui-react-native/package.json')"`
)
require File.join(transcend_pkg, 'setup-scripts/ios/embed_transcend_headless_framework')- Inside your Podfile's app target block, add a
post_integratehook (alongside any existingpost_install):
post_integrate do |installer|
TranscendConsentUiReactNative::Ios.embed_headless_framework!(installer)
end- Run
pod installagain so the hook patches the embed-frameworks script.
Without this hook, the app may build but crash at launch with a dyld error loading TranscendHeadless.framework.
Android
No extra repository configuration is required. The native Android Headless SDK (io.transcend.sdk.headless:headless) is included as a transitive dependency. INTERNET and ACCESS_NETWORK_STATE permissions are declared automatically.
TurboModule codegen
The npm package ships TurboModule spec files under codegen/ (not src/). React Native autolinking reads codegenConfig.jsSrcsDir from the package manifest and runs codegen against that folder during your app build — no extra paths or tarball unpacking steps are required.
Install from a registry version or a local tarball:
yarn add @transcend-io/cm-mobile-ui-react-native
# or
yarn add /path/to/transcend-io-cm-mobile-ui-react-native-0.9.0.tgzAfter adding the dependency, rebuild native projects (pod install on iOS, Gradle sync/build on Android) so codegen picks up the spec.
Troubleshooting: native module not found
If initialization fails with an error like:
[ConsentSDK] The native module "TranscendConsentUiReactNative" could not be found.or the older React Native message:
TurboModuleRegistry.getEnforcing(...): 'TranscendConsentUiReactNative' could not be foundwork through this checklist:
- Rebuild the native app — restarting Metro alone is not enough after installing or upgrading the package.
- iOS: run
pod installand confirm thepost_integrateembed hook from the iOS setup section is in your Podfile. - Android: run a Gradle sync/build so autolinking picks up the package.
- Use a custom native build — Expo Go does not include this SDK. Use a dev client or bare React Native app.
- Confirm React Native >= 0.75 and that you are on a supported architecture (see Architecture support above). On RN 0.82+, Old Architecture is not available; a module-not-found error on those versions usually means a build or linking issue, not an architecture mismatch.
Initialization
Wrap your app in ConsentProvider and pass a config object. The provider initializes the native SDK on mount and exposes readiness through isReady.
import { ConsentProvider } from '@transcend-io/cm-mobile-ui-react-native';
export default function App() {
return (
<ConsentProvider
config={{
bundleId: 'YOUR_BUNDLE_ID', // Required — Transcend bundle ID
mobileAppId: 'com.example.myapp', // Required — your app's mobile identifier from https://app.transcend.io/consent-manager/native/applications
isTest: false, // Use test or production deployment
cdnUrl: 'https://transcend-cdn.com', // Optional — defaults to Transcend's CDN
token: 'AUTH_TOKEN', // Optional — auth token for backend sync
settingsOverrides: {
// Optional — override remote settings at init
country: 'EU',
regime: 'GDPR',
},
}}
onError={(error) => {
// Fires when SDK initialization fails
console.error('Consent SDK init failed:', error);
}}
>
<YourApp />
</ConsentProvider>
);
}Handling initialization errors
Pass an onError callback to ConsentProvider to be notified when initialization fails (e.g. invalid config, network error, or native SDK init rejection). When onError fires, isReady remains false and consent APIs should not be called.
<ConsentProvider
config={{ bundleId: '...', mobileAppId: '...' }}
onError={(error) => {
// Log, report to your error tracker, or show a fallback UI
reportError('consent_init_failed', error);
}}
>
<YourApp />
</ConsentProvider>onError is invoked once per failed init attempt. Use it alongside the isReady check in child components — isReady tells you when it is safe to proceed; onError tells you why initialization did not succeed.
config fields (InitSdkOptions)
| Field | Required | Description |
| ------------------- | -------- | -------------------------------------------------------------------------------------------- |
| bundleId | Yes | Transcend bundle ID. |
| mobileAppId | Yes | Native app identifier registered in Transcend. |
| isTest | No | Use test deployment. Default: false. |
| cdnUrl | No | CDN base URL. Default: https://transcend-cdn.com. |
| token | No | Auth token for backend sync. Required for sync to work. |
| settingsOverrides | No | Runtime overrides for remote settings (see Registering Overrides). |
| destroyOnClose | No | Android native config flag (default: false). Does not destroy on React unmount — call destroy() from useConsent when you need teardown. |
Memoize the config object (especially settingsOverrides) when values are stable across renders. ConsentProvider only re-initializes when init fields change, not when the config object identity changes.
Offline CDN caching (cacheTtlMs)
ConsentProvider fetches ui-mobile-load-options.json once and uses that payload for both the consent UI and native SDK init, so the native layer does not make a second CDN request. Locale translation JSONs and consent UI logo images are cached the same way. When offline, the SDK falls back to the last cached copy.
cacheTtlMs controls this shared fetch (and the other JS-layer CDN assets).
| cacheTtlMs | Behavior |
| ------------ | -------- |
| omitted (default) | Caching on; entries do not expire. |
| > 0 (e.g. 10800000 for 3h) | Caching on; offline fallback only uses entries younger than the TTL. Network refresh is still attempted when online. |
| 0 | Caching off; network-only behavior. |
<ConsentProvider
config={{ bundleId: '...', mobileAppId: '...' }}
cacheTtlMs={10_800_000}
>
<YourApp />
</ConsentProvider>Use clearOfflineCache() from the package to wipe cached CDN assets (e.g. on logout or for debugging).
Checking Readiness
Use the isReady signal from useConsent to know when the SDK has finished loading settings, translations, and UI assets. Do not call consent APIs until isReady is true.
Your app UI inside ConsentProvider renders during initialization. Use isReady (and consent === null before native init completes) to avoid calling headless APIs too early.
import { useEffect } from 'react';
import { useConsent } from '@transcend-io/cm-mobile-ui-react-native';
function TrackerLoader() {
const { isReady, getSdkConsentStatus } = useConsent();
useEffect(() => {
if (!isReady) return;
void getSdkConsentStatus('analytics_sdk').then((status) => {
if (status === 'ALLOW') {
// Safe to initialize the analytics tracker
}
});
}, [isReady, getSdkConsentStatus]);
return null;
}If initialization fails, isReady remains false and the onError callback passed to ConsentProvider is invoked with the error. Use onError to log or surface the failure; gate all consent and tracker logic on isReady.
Getting and Setting Consent
Get consent
Read the current consent from the consent object exposed by useConsent. This is React state kept in sync after initialization, sync, and after setConsent calls.
const { consent } = useConsent();
// consent.confirmed — boolean
// consent.prompted — boolean
// consent.purposeMap — Record<string, boolean>
// consent.purposes — string[] of granted purpose keys
// consent.timestamp — ISO 8601 string (optional)Set consent
Pass a map of purpose names to boolean values. Syncs to the backend by default when a token was provided at init.
const { setConsent } = useConsent();
const success = await setConsent(
{
Analytics: true,
Functional: true,
Advertising: false,
},
{
autoSync: true, // Sync to backend after setting (default: true)
confirmed: true, // Mark consent as confirmed (default: true)
timestamp: undefined, // ISO Timestamp associated with the consent change (default: now)
updated: true, // Mark consent as updated (default: true)
}
);After setConsent resolves, the consent object in context is refreshed automatically.
Headless setConsent, acceptAll, and rejectAll do not close the bundled consent UI. UI buttons (Accept All, Reject All, Save) close the banner/modal after a successful mutation. To close programmatically after a headless update, call hideConsentManager() from useConsentUI.
Registering Overrides
Pass overrides in config.settingsOverrides at initialization to test specific regimes, disable sync, or force configuration values.
<ConsentProvider
config={{
bundleId: 'YOUR_BUNDLE_ID',
mobileAppId: 'com.example.myapp',
settingsOverrides: {
regime: 'GDPR',
},
}}
>
<YourApp />
</ConsentProvider>Common override keys include: "partition", "regime", "defaultRegime".
Syncing Consent
Backend sync requires an auth token. Pass token in config at init — without it, sync is skipped and consent is not pushed to or pulled from the backend. Generate a token on your backend server following the Transcend preference store docs.
<ConsentProvider
config={{
bundleId: 'YOUR_BUNDLE_ID',
mobileAppId: 'com.example.myapp',
token: 'AUTH_TOKEN', // Required for sync
}}
>
<YourApp />
</ConsentProvider>The native SDK syncs consent automatically in two cases:
- During initialization using the
tokenfromconfig. - After
setConsentwhenautoSyncistrue(the default).
There is no separate public sync() method. To trigger a sync, call setConsent with autoSync: true and ensure a valid token was provided at init.
Sync is also skipped when SYNC_DISABLED is true in an override.
After a successful sync during init, isReady becomes true and consent reflects any remote changes.
SDK Consent Status
Use these APIs to determine whether a third-party SDK (identified by a service ID) is allowed to run given the current consent state. Gate tracker initialization on isReady, then check status before loading each SDK.
Recommended pattern
import { useEffect } from 'react';
import { useConsent } from '@transcend-io/cm-mobile-ui-react-native';
function AnalyticsTracker() {
const { isReady, getSdkConsentStatus } = useConsent();
useEffect(() => {
if (!isReady) return;
void getSdkConsentStatus('analytics_sdk').then((status) => {
if (status === 'ALLOW') {
// Initialize analytics SDK
}
});
}, [isReady, getSdkConsentStatus]);
return null;
}Re-run tracker checks whenever isReady transitions to true (e.g. after init) or when consent changes and you need to re-evaluate SDK permissions.
Single service
const { getSdkConsentStatus } = useConsent();
const status = await getSdkConsentStatus('analytics_sdk');
switch (status) {
case 'ALLOW':
// SDK may run
break;
case 'BLOCK':
// User denied required purpose(s)
break;
case 'NO_SDK_FOUND':
// Service ID not in purpose map
break;
case 'INTERNAL_ERROR':
// Error occurred
break;
}Batch lookup
Pass an array of service IDs to evaluate multiple SDKs at once:
const { batchGetSdkConsentStatuses } = useConsent();
const statusMap = await batchGetSdkConsentStatuses([
'analytics_sdk',
'ads_sdk',
]);
// e.g. { analytics_sdk: 'ALLOW', ads_sdk: 'BLOCK' }ConsentStatus values
| Status | Meaning |
| ---------------- | ------------------------------------------------------------------------------------ |
| ALLOW | All required purposes for this SDK are granted (or SDK is Essential). |
| BLOCK | At least one required purpose was explicitly denied. |
| NO_SDK_FOUND | The service ID is not in the SDK purpose map. |
| INTERNAL_ERROR | Consent resolution encountered an error while reconciling the service ID's purposes. |
The SDK purpose map is loaded from settings and cached by the native SDK (refreshed every 3 hours).
Consent UI (useConsentUI)
UI orchestration APIs live on useConsentUI, not useConsent:
import { useConsent, useConsentUI } from '@transcend-io/cm-mobile-ui-react-native';
const { isReady } = useConsent();
const {
showConsentManager,
hideConsentManager,
autoShowConsentManager,
openPreferences,
} = useConsentUI();
if (!isReady) return null;
showConsentManager(); // Opens default experience
showConsentManager({ viewState: 'banner-into-modal' }); // Opens a specific variantTeardown (destroy)
To tear down the native SDK (e.g. logout, before re-initialization), call destroy() from useConsent. This is a heavy operation — it is not invoked automatically when ConsentProvider unmounts.
const { destroy } = useConsent();
await destroy(); // Closes UI, calls native TranscendAPI.destroy(), resets JS stateAfter destroy(), remount ConsentProvider when you need a clean re-init. With destroyOnClose: false (default), the native SDK persists across provider remounts until you call destroy().
Additional headless APIs
const { acceptAll, rejectAll } = useConsent();
await acceptAll(); // Does not close the consent UI
await rejectAll();For direct native access without the React context (advanced usage), import NativeConsent from the package. This bypasses the consent / isReady React state and should only be used when you manage state yourself.
Error Handling
| Scenario | Behavior |
| -------------------------------------- | ---------------------------------------------------------------------------------- |
| Missing bundleId or mobileAppId | onError invoked; isReady stays false. |
| Native init failure | onError invoked with the error; also logged to console; isReady stays false. |
| Translation load failure | onError invoked with ERR_LOAD_TRANSLATIONS; isReady stays false. |
| API called before ready | Headless APIs return safe defaults or false; gate on isReady for real data. |
| useConsent outside ConsentProvider | Throws: "useConsent must be used inside ConsentProvider". |
| useConsentUI outside provider | Throws: "useConsentUI must be used inside ConsentProvider". |
