EXPO SDK 52 expo-router v4 TypeScript

Expo DOM Components — 'use dom'

Guía de integración para CultivaDash: ejecuta componentes React web (recharts, syntax highlighters, editores) dentro de WebViews en iOS y Android sin reescribir código.

Casos de uso

USAR 'use dom' cuando...

  • Librerías que requieren DOM (recharts, chart.js)
  • Migrar componentes web existentes a nativo
  • CSS avanzado no disponible en React Native
  • Canvas / WebGL / iframes / embeds externos
  • Editores de texto enriquecido (quill, tiptap)

NO USAR 'use dom' cuando...

  • Performance nativa crítica (listas largas, animaciones)
  • UI simple que React Native soporta bien
  • Integración profunda con APIs nativas
  • Archivos _layout de expo-router
  • Componentes con estado compartido síncrono
5 reglas del DOM Component
1
'use dom'; directiva
Primera línea del archivo, obligatoria
2
Default export único
Un componente React por archivo
3
Archivo propio
No inline, no combinado con nativo
4
Props serializables
string, number, boolean, arrays, plain objects
5
CSS aislado
Imports CSS dentro del mismo archivo
Comunicación bidireccional nativo ↔ DOM
Nativo
app/analytics.tsx
iOS · Android · Web
───── Props serializable ──→
campaignData[], userId, dom={}
←── async fn callbacks ──────
onExportCSV(), onAlert()
DOM Component
CampaignChart.tsx
'use dom' · WebView
Componente CultivaDash — CampaignChart.tsx
components/CampaignChart.tsx TSX · DOM
"use dom";                           // ← directiva obligatoria

import { LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid } from "recharts";

interface CampaignData {
  day: string;
  conversions: number;
  spend: number;
}

interface Props {
  campaignData: CampaignData[];
  onExportCSV: (rows: object[]) => Promise<void>;  // llama Share nativo
  currentUserId: string;
  dom: import("expo/dom").DOMProps;
}

export default function CampaignChart({ campaignData, onExportCSV, currentUserId }: Props) {
  return (
    <div style={{ padding: 20, fontFamily: "system-ui", background: "#0d1117" }}>
      <h3 style={{ color: "#e6edf3", marginBottom: 16 }}>
        Conversiones — {currentUserId}
      </h3>

      <LineChart width={360} height={220} data={campaignData}>
        <CartesianGrid strokeDasharray="3 3" stroke="#30363d" />
        <XAxis dataKey="day" stroke="#8b949e" />
        <YAxis stroke="#8b949e" />
        <Tooltip />
        <Line dataKey="conversions" stroke="#3fb950" strokeWidth={2} />
        <Line dataKey="spend" stroke="#58a6ff" strokeWidth={2} />
      </LineChart>

      <button onClick={() => onExportCSV(campaignData)}>
        Exportar CSV
      </button>
    </div>
  );
}
app/(tabs)/analytics.tsx TSX · NATIVO
import { View, Text, Share } from "react-native";
import { useLocalSearchParams } from "expo-router";
import CampaignChart from "@/components/CampaignChart";  // DOM component

const CAMPAIGN_DATA = [
  { day: "Lun", conversions: 42, spend: 180 },
  { day: "Mar", conversions: 67, spend: 220 },
  { day: "Mié", conversions: 55, spend: 195 },
  { day: "Jue", conversions: 89, spend: 310 },
  { day: "Vie", conversions: 112, spend: 380 },
];

export default function AnalyticsScreen() {
  const { userId } = useLocalSearchParams();  // no disponible en DOM, pasar como prop

  return (
    <View style={{ flex: 1, background: "#0d1117" }}>
      <Text>CultivaDash Analytics</Text>

      <CampaignChart
        campaignData={CAMPAIGN_DATA}
        currentUserId={userId as string}
        onExportCSV={async (rows) => {
          await Share.share({ message: JSON.stringify(rows) });
        }}
        dom={{ style: { height: 320 }, scrollEnabled: false }}
      />
    </View>
  );
}
Vista previa del chart (recharts en WebView)

CultivaDash — Conversiones & Spend semana actual 'use dom'

0 100 200 300 400 Lun Mar Mié Jue Vie Viernes conv: 112 spend: 380
LEYENDA
Conversiones
Gasto (€)
Total sem.:
365 conv.
1.285 €
CPC: 3,52 €
Tres DOM Components para CultivaDash
CampaignChart
CampaignChart.tsx
Gráfico de líneas con recharts. Muestra conversiones y spend semanal. Callback onExportCSV llama a Share nativo.
recharts Share nativa
ApiCodeBlock
ApiCodeBlock.tsx
Syntax highlighting con react-syntax-highlighter para mostrar snippets del SDK de CULTIVA IA. Tema dark Dracula.
syntax-highlighter DOM only
CampaignNotes
CampaignNotes.tsx
Editor rich text (tiptap) para notas de campaña con HTML básico. Guarda via callback async a AsyncStorage nativo.
tiptap AsyncStorage
Comportamiento por plataforma
Plataforma Render dom prop Performance
iOS 16+ WKWebView aplicada 60fps para charts pequeños
Android 13+ WebView (Chromium) aplicada 60fps, mayor consumo RAM
Web (Expo Web) Directo en DOM ignorada Nativo del browser
Tips de implementacion
⚡
Mantén DOM components enfocados
No pongas pantallas enteras en WebViews. Usa componentes nativos para navegación y DOM components solo para contenido especializado.
🔄
Hot reload funciona en desarrollo
Los DOM components se actualizan automáticamente con Fast Refresh igual que componentes nativos durante el desarrollo.
🛣️
useLocalSearchParams → pasar como prop
Los hooks de routing no funcionan en DOM components. Lee el valor en el padre nativo y pásalo como prop serializable.
🎨
CSS en contexto aislado
Cada DOM component tiene su propio contexto JS. Incluye los imports de CSS dentro del mismo archivo del componente.