Ir al contenido

React Native

El SDK incluye un entry point dedicado para React Native@magicfeedback/popup-sdk/react-native — que hace toda la integración por ti: almacenamiento persistente, device info, plataforma, ciclo de vida de la app, captura de errores y renderizado de surveys en un Modal + WebView.

En la mayoría de apps la integración es un solo componente: envuelve tu app en <DeepdotsProvider>.

Ventana de terminal
npm install @magicfeedback/popup-sdk react-native-webview

Dos paquetes opcionales desbloquean el resto de funcionalidades. Instala ambos salvo que tengas un motivo para no hacerlo:

Ventana de terminal
npm install react-native-mmkv react-native-device-info
Ventana de terminal
cd ios && pod install
PaqueteObligatorioQué aporta
react-native-webviewRenderiza la UI del survey
react-native-mmkvRecomendadoPersiste el user_id entre reinicios de la app (usuarios recurrentes)
react-native-device-infoRecomendadoTipo de dispositivo, versión de OS, modelo, versión de app (métricas de Technology)

Todos están declarados como peer dependencies opcionales: el SDK detecta en runtime lo que hay instalado y degrada con elegancia. Sin MMKV el user_id solo vive en memoria y se genera uno nuevo en cada arranque; sin react-native-device-info se omite el contexto de dispositivo.

App.tsx
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',
// Omite `analytics` para quedarte en modo dry-run (eventos por consola, sin envío).
analytics: {
publicKey: 'YOUR_ANALYTICS_PUBLIC_KEY',
integration: 'YOUR_INTEGRATION_ID',
},
}}
>
<YourNavigation />
</DeepdotsProvider>
);
}

config acepta el mismo objeto que init(config) — el Provider rellena por ti los campos específicos de React Native (storage, device, platform).

AspectoCómo
Identidad persistenteInstancia de MMKV (id: 'deepdots-sdk') si react-native-mmkv está instalado
Device inforeact-native-device-info si está instalado
PlataformaPlatform.OS'ios' / 'android' en el contexto de analytics
Tiempo de engagementAppStateonForeground() / onBackground() (hace flush al pasar a background)
Captura de erroresglobal.ErrorUtils → errores JS no manejados como deepdots_app_crash
Render del surveyReactNativePopupRenderer + un Modal con un WebView, montado on demand

Dos cosas no son automáticas y necesitan unas líneas tuyas: el tracking de navegación y arrancar los triggers.

useDeepdots() devuelve la instancia compartida de DeepdotsPopups desde cualquier punto bajo el Provider. Toda la API de analítica está disponible — ver la guía de Analytics.

import { useDeepdots } from '@magicfeedback/popup-sdk/react-native';
function ProductScreen() {
const dd = useDeepdots();
return (
<Button
title="Añadir al carrito"
onPress={() => {
dd.track('add_to_cart', { product_id: 'p-123', value: 49.9 });
dd.triggerEvent('added_to_cart');
}}
/>
);
}

El Provider inicializa el SDK y descarga las definiciones de popup, pero no arranca los triggers — llama a autoLaunch() una vez desde un componente dentro del Provider:

function DeepdotsBootstrap() {
const dd = useDeepdots();
useEffect(() => {
dd.autoLaunch();
}, [dd]);
return null;
}

Como alternativa, sáltate autoLaunch() y muestra los popups de forma imperativa con dd.triggerEvent('some_event') — los triggers de tipo event que coincidan se disparan inmediatamente.

Un popup restringido a ciertos idiomas (segments.lang) se evalúa contra el idioma que el SDK resolvió en init(): tu language explícito, si no navigator.language, si no el locale de Intl. Ese último fallback es lo que hace que funcione bajo Hermes, donde navigator.language no existe. La comparación es por prefijo, así que un segmento en coincide con en-US.

Si prefieres controlar la segmentación explícitamente en lugar de depender de la detección — recomendable cuando tu app tiene su propio i18n — pasa el idioma en la config:

<DeepdotsProvider config={{ apiKey: 'YOUR_PUBLIC_API_KEY', language: 'es-ES' }}>

Cuando se muestra un popup, el Provider monta un Modal a pantalla completa con un WebView con el survey. El HTML del survey es autocontenido pero carga @magicfeedback/native desde un CDN, así que renderizar un survey requiere conexión. El estado del popup (SHOWED / PARTIAL / COMPLETED) se reporta a POST /sdk/popups automáticamente, y el user_id persistente, el session_id y el mini_service activo se inyectan en la metadata de identidad del survey.

React Native no tiene History API, así que las vistas de pantalla no se detectan solas. Repórtalas con setScreen(name) — el SDK emite un evento deepdots_page_view con la duración de la pantalla anterior cuando reportas la siguiente.

Con 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>
);
}

Los nombres de pantalla se normalizan igual que las rutas web: se descarta el query string y los segmentos numéricos/UUID se colapsan a :id, así que /product/123 y /product/456 se reportan ambos como /product/:id.

Avanzado: configuración manual sin el Provider

Sección titulada «Avanzado: configuración manual sin el Provider»

Si necesitas tu propia superficie (un bottom sheet, una pantalla dedicada) o tu propio árbol de React, usa setupReactNative() y el ReactNativePopupRenderer directamente. Ambos vienen del entry point principal.

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;
// Fija el renderer, inyecta storage/device/platform, llama a init()
// y cablea AppState → onForeground/onBackground. Devuelve una función de limpieza.
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}
</>
);
}

El renderer es un puente, no un stub: onShow te entrega { surveyId, productId, html } listo para <WebView source={{ html }}>, y handleMessage traduce los mensajes del WebView a eventos del SDK — primera interacción → popup_clicked (PARTIAL), completado → survey_completed (COMPLETED).

Renderizar el survey sin la tarjeta del SDK (renderChrome)

Sección titulada «Renderizar el survey sin la tarjeta del SDK (renderChrome)»

Desde 1.4.0, cuando montas tu propio contenedor decorado (un Modal, un bottom sheet o una pantalla con su propia tarjeta, fondo, bordes redondeados o backdrop), pasa renderChrome: false en la config:

setupReactNative(
sdk,
{ apiKey: 'YOUR_PUBLIC_API_KEY', renderChrome: false },
{ /* deps */ },
);

Desde 1.3.0 el HTML del survey pinta su propia tarjeta y backdrop (header con botón de cerrar, footer con los botones de navegación, tarjeta redondeada, fondo oscurecido). Si tu contenedor también está decorado, los dos se apilan en un “doble modal” — una tarjeta dentro de otra. renderChrome: false deja el HTML del WebView transparente y a pantalla completa para que llene tu contenedor, manteniendo el survey totalmente funcional (puente de mensajes, formulario, botones back/start/complete/send y el botón de cerrar). Tú controlas el marco exterior; el SDK controla el survey.

El flag solo afecta a React Native (el HTML del survey en el WebView). No afecta al popup DOM web, y el <DeepdotsProvider> por defecto lo ignora, porque su Modal ya es transparente y a pantalla completa (así que el camino integrado muestra una sola tarjeta). Úsalo en el camino manual de arriba.

renderChrome decide quién pinta el marco. Para reestilar lo que va dentro — enunciados, opciones, escalas de valoración, y la cabecera, la barra de progreso y el footer del propio SDK — pasa tu hoja de estilos en surveyCss. Se inyecta como última hoja del WebView, así que gana en cascada sin tocar los valores por defecto que comparten todos los clientes de Deepdots.

Esa página también lista los nombres de clase a los que apuntar. Desde la 1.5.0 el SDK expone los mismos puntos de enganche en el popup web y en este WebView, así que una sola hoja de estilos cubre las dos plataformas.

DepTipoValor por defecto si se omite
mmkvInstancia de MMKVconfig.storage, si no en memoria
deviceInfoMódulo react-native-device-infoconfig.device, si no se omite
appStateAppStateSin cableado de ciclo de vida
platform'ios' / 'android' / 'web'config.platform, si no 'web'
rendererPopupRendererEl renderer por defecto del SDK
errorUtilsglobal.ErrorUtilsglobalThis.ErrorUtils si existe

Todas las deps son opcionales. También se exportan los helpers de más bajo nivel si prefieres montar las piezas a mano: mmkvStorage(mmkv) (adaptador síncrono de KeyValueStorage), collectRnDevice(deviceInfo) y buildSurveyHtml(options).