React Native
The SDK ships a dedicated React Native entry point — @magicfeedback/popup-sdk/react-native — that does the whole integration for you: persistent storage, device info, platform, app lifecycle, crash capture, and rendering surveys in a Modal + WebView.
For most apps the integration is one component: wrap your app in <DeepdotsProvider>.
Installation
Section titled “Installation”npm install @magicfeedback/popup-sdk react-native-webviewTwo optional packages unlock the rest of the feature set. Install both unless you have a reason not to:
npm install react-native-mmkv react-native-device-infocd ios && pod install| Package | Required | What it gives you |
|---|---|---|
react-native-webview | ✅ | Renders the survey UI |
react-native-mmkv | Recommended | Persists the user_id across app restarts (returning users) |
react-native-device-info | Recommended | Device type, OS version, model, app version (Technology metrics) |
All of them are declared as optional peer dependencies: the SDK detects what is installed at runtime and degrades gracefully. Without MMKV the user_id lives in memory only and a fresh one is generated on every launch; without react-native-device-info the device context is omitted.
Quick start
Section titled “Quick start”import { DeepdotsProvider } from '@magicfeedback/popup-sdk/react-native';
export default function App() { return ( <DeepdotsProvider config={{ apiKey: 'YOUR_PUBLIC_API_KEY', nodeEnv: __DEV__ ? 'development' : 'production', userId: 'customer-123', appVersion: '1.4.0', // Omit `analytics` to stay in dry-run mode (events logged, nothing sent). analytics: { publicKey: 'YOUR_ANALYTICS_PUBLIC_KEY', integration: 'YOUR_INTEGRATION_ID', }, }} > <YourNavigation /> </DeepdotsProvider> );}config accepts the same object as init(config) — the Provider fills in the React Native specific fields (storage, device, platform) for you.
What the Provider wires automatically
Section titled “What the Provider wires automatically”| Concern | How |
|---|---|
| Persistent identity | MMKV instance (id: 'deepdots-sdk') when react-native-mmkv is installed |
| Device info | react-native-device-info when installed |
| Platform | Platform.OS → 'ios' / 'android' in the analytics context |
| Engagement time | AppState → onForeground() / onBackground() (flushes on background) |
| Crash capture | global.ErrorUtils → unhandled JS errors as deepdots_app_crash |
| Survey rendering | ReactNativePopupRenderer + a Modal with a WebView, mounted on demand |
Two things are not automatic and need a few lines from you: navigation tracking and starting the triggers.
Accessing the SDK
Section titled “Accessing the SDK”useDeepdots() returns the shared DeepdotsPopups instance from anywhere under the Provider. The full analytics API is available — see the Analytics guide.
import { useDeepdots } from '@magicfeedback/popup-sdk/react-native';
function ProductScreen() { const dd = useDeepdots();
return ( <Button title="Add to cart" onPress={() => { dd.track('add_to_cart', { product_id: 'p-123', value: 49.9 }); dd.triggerEvent('added_to_cart'); }} /> );}Showing popups
Section titled “Showing popups”The Provider initializes the SDK and fetches the popup definitions, but it does not start the triggers — call autoLaunch() once from a component inside the Provider:
function DeepdotsBootstrap() { const dd = useDeepdots();
useEffect(() => { dd.autoLaunch(); }, [dd]);
return null;}Alternatively, skip autoLaunch() and show popups imperatively with dd.triggerEvent('some_event') — matching event triggers fire immediately.
Language segments
Section titled “Language segments”A popup restricted to certain languages (segments.lang) is evaluated against the language the SDK resolved at init(): your explicit language, else navigator.language, else the Intl locale. That last fallback is what makes it work under Hermes, where navigator.language does not exist. Matching is by prefix, so a segment of en matches en-US.
If you want to control targeting explicitly instead of relying on detection — recommended when your app has its own i18n — pass the language in the config:
<DeepdotsProvider config={{ apiKey: 'YOUR_PUBLIC_API_KEY', language: 'es-ES' }}>When a popup is shown, the Provider mounts a full-screen Modal containing a WebView with the survey. The survey HTML is self-contained but loads @magicfeedback/native from a CDN, so rendering a survey requires network access. Popup status (SHOWED / PARTIAL / COMPLETED) is reported to POST /sdk/popups automatically, and the persistent user_id, session_id, and active mini_service are injected into the survey’s identity metadata.
Navigation tracking
Section titled “Navigation tracking”React Native has no History API, so screen views are not detected automatically. Report them with setScreen(name) — the SDK emits a deepdots_page_view event with the duration of the previous screen when you report the next one.
With React Navigation:
import { NavigationContainer, createNavigationContainerRef } from '@react-navigation/native';import { useDeepdots } from '@magicfeedback/popup-sdk/react-native';
const navRef = createNavigationContainerRef();
function Navigation() { const dd = useDeepdots();
const report = () => { const route = navRef.getCurrentRoute(); if (route) dd.setScreen(route.name); };
return ( <NavigationContainer ref={navRef} onReady={report} onStateChange={report}> <YourStack /> </NavigationContainer> );}Screen names are normalized the same way as web paths: query strings are dropped and numeric/UUID segments collapse to :id, so /product/123 and /product/456 both report as /product/:id.
Advanced: manual setup without the Provider
Section titled “Advanced: manual setup without the Provider”If you need your own surface (a bottom sheet, a dedicated screen) or your own React tree, use setupReactNative() and the ReactNativePopupRenderer directly. Both come from the main entry point.
import { useEffect, useRef, useState } from 'react';import { AppState, Modal, Platform, View } from 'react-native';import { WebView } from 'react-native-webview';import { MMKV } from 'react-native-mmkv';import DeviceInfo from 'react-native-device-info';import { DeepdotsPopups, ReactNativePopupRenderer, setupReactNative, type ReactNativeSurveyPayload,} from '@magicfeedback/popup-sdk';
const sdk = new DeepdotsPopups();
export function DeepdotsHost({ children }: { children: React.ReactNode }) { const [survey, setSurvey] = useState<ReactNativeSurveyPayload | null>(null); const rendererRef = useRef<ReactNativePopupRenderer | null>(null);
useEffect(() => { const renderer = new ReactNativePopupRenderer({ onShow: (payload) => setSurvey(payload), onHide: () => setSurvey(null), }); rendererRef.current = renderer;
// Sets the renderer, injects storage/device/platform, calls init() // and wires AppState → onForeground/onBackground. Returns a cleanup fn. return setupReactNative( sdk, { apiKey: 'YOUR_PUBLIC_API_KEY', nodeEnv: __DEV__ ? 'development' : 'production' }, { mmkv: new MMKV({ id: 'deepdots-sdk' }), deviceInfo: DeviceInfo, appState: AppState, platform: Platform.OS === 'ios' ? 'ios' : 'android', renderer, }, ); }, []);
return ( <> {children} {survey ? ( <Modal visible transparent animationType="slide"> <View style={{ flex: 1 }}> <WebView style={{ flex: 1 }} originWhitelist={['*']} javaScriptEnabled source={{ html: survey.html, baseUrl: 'https://sdk.deepdots.com/' }} onMessage={(e) => rendererRef.current?.handleMessage(e.nativeEvent.data)} /> </View> </Modal> ) : null} </> );}The renderer is a bridge, not a stub: onShow hands you { surveyId, productId, html } ready for <WebView source={{ html }}>, and handleMessage translates the WebView messages into SDK events — first interaction → popup_clicked (PARTIAL), completion → survey_completed (COMPLETED).
Rendering the survey without the SDK’s card (renderChrome)
Section titled “Rendering the survey without the SDK’s card (renderChrome)”From 1.4.0, when you mount your own decorated container (a Modal, bottom sheet, or screen with its own card, background, rounded corners, or backdrop), pass renderChrome: false in the config:
setupReactNative( sdk, { apiKey: 'YOUR_PUBLIC_API_KEY', renderChrome: false }, { /* deps */ },);Since 1.3.0 the survey HTML draws its own card and backdrop (header with a close button, footer with the navigation buttons, rounded card, dimmed background). If your own container is also decorated, the two stack into a “double modal” — a card inside a card. renderChrome: false makes the WebView HTML transparent and edge-to-edge so it fills your container, while keeping the survey fully functional (message bridge, form, back/start/complete/send buttons, and the close button). You own the outer frame; the SDK owns the survey.
The flag only affects React Native (the survey WebView HTML). It has no effect on the web DOM popup, and it is ignored by the default <DeepdotsProvider>, whose Modal is already transparent and full-screen (so the built-in path shows a single card). Use it on the manual path shown above.
Restyling the survey itself
Section titled “Restyling the survey itself”renderChrome decides who draws the frame. To restyle what is inside it — question wording, options, rating scales, and the SDK’s own header, progress bar and footer — pass your own stylesheet in surveyCss. It is injected as the last stylesheet in the WebView, so it wins the cascade without touching the defaults shared by every Deepdots customer.
That page also lists the class names to target. From 1.5.0 the SDK exposes the same hooks in both the web popup and this WebView, so one stylesheet covers both platforms.
setupReactNative(sdk, config, deps)
Section titled “setupReactNative(sdk, config, deps)”| Dep | Type | Default when omitted |
|---|---|---|
mmkv | MMKV instance | config.storage, else in-memory |
deviceInfo | react-native-device-info module | config.device, else omitted |
appState | AppState | No lifecycle wiring |
platform | 'ios' / 'android' / 'web' | config.platform, else 'web' |
renderer | PopupRenderer | The SDK’s default renderer |
errorUtils | global.ErrorUtils | globalThis.ErrorUtils if present |
Every dep is optional. Lower-level helpers are exported too if you want to build the pieces yourself: mmkvStorage(mmkv) (a synchronous KeyValueStorage adapter), collectRnDevice(deviceInfo), and buildSurveyHtml(options).