jueves, 26 de junio de 2025

Interacción entre React y un componente fuera de React

 ¿Se puede usar un componente creado con HTML, CSS y Javascript clásico, dentro de una aplicación web de React e Inertia? ¿Qué implicancias tiene?


¡Sí, es completamente posible usar un componente creado con HTML, CSS y JavaScript clásico dentro de una aplicación web de React e Inertia! Es una técnica común, especialmente en la migración de aplicaciones legacy o cuando necesitas integrar código de terceros que no está en React.


¿Cómo se logra?

La forma principal de integrar código JavaScript/HTML/CSS clásico en React es a través de las siguientes técnicas:

  1. Referencias (Refs) para Acceso Directo al DOM:

    • Puedes crear una referencia a un elemento DOM en tu componente React.
    • Luego, en el useEffect (o componentDidMount en componentes de clase) de tu componente React, accedes a ese elemento DOM directamente a través de la ref.
    • Una vez que tienes el elemento DOM, puedes manipularlo con JavaScript clásico, añadirle escuchadores de eventos o inicializar librerías de terceros que esperen un elemento DOM.
  2. dangerouslySetInnerHTML (con precaución):

    • Puedes inyectar una cadena de HTML puro directamente en el DOM. React renderizará esa cadena como HTML real.
    • ¡Advertencia! Su nombre dangerouslySetInnerHTML no es casual. Es peligroso si la cadena HTML proviene de una fuente no confiable, ya que expone tu aplicación a ataques de Cross-Site Scripting (XSS). Úsala solo si el HTML es estático o proviene de una fuente totalmente segura controlada por ti.

Implicaciones de usar Componentes Clásicos en React/Inertia:

Integrar código clásico tiene varias implicaciones importantes que debes considerar:

  1. Manejo del Ciclo de Vida:

    • Inicialización: El JavaScript clásico se debe inicializar después de que el elemento DOM al que apunta esté disponible en el árbol de React. Por eso, useEffect (con un array de dependencias vacío [] para que se ejecute solo una vez al montarse el componente) es crucial.
    • Actualizaciones: Si el componente clásico necesita reaccionar a cambios en las props de React, tendrás que pasar esas props a tu JavaScript clásico y re-inicializar o actualizar el componente clásico dentro de otro useEffect que dependa de esas props.
    • Limpieza: Es vital que en la función de limpieza de useEffect (el return del callback), desmonte o destruyas cualquier instancia del componente clásico o escuchador de eventos que hayas inicializado. Si no lo haces, podrías tener pérdidas de memoria o comportamientos inesperados cuando el componente React se desmonte o se re-renderice.
  2. Estado y Datos:

    • Estado Separado: El estado gestionado por tu JavaScript clásico estará completamente separado del estado de React. No se comunicarán automáticamente.
    • Comunicación: Si necesitas que el componente clásico notifique a React sobre un cambio, el componente clásico deberá disparar un evento DOM (CustomEvent) que tu componente React escuchará, o llamar a una función de callback que le pases como prop desde React.
    • Props a Clásico: Si el componente clásico necesita datos de React, deberás pasárselos al inicializarlo o a través de actualizaciones manuales.
  3. Rendimiento:

    • Menos Optimización: React y Next.js tienen optimizaciones de renderizado y reconciliación (Virtual DOM). Cuando manipulas el DOM directamente con JavaScript clásico, estás "saliéndote" del flujo de React, lo que puede anular algunas de estas optimizaciones para esa parte de la aplicación.
    • Recargas de Páginas (Inertia): Si el componente clásico es un script global o una librería que se adjunta al body o a elementos persistentes, tené en cuenta que Inertia no recarga la página completa en las navegaciones. Esto es bueno, pero significa que si ese script clásico no está diseñado para ser "desmontado y remontado" con un componente React, podría haber conflictos o duplicación de comportamiento. Necesitarás una buena lógica de limpieza.
  4. Estilos (CSS):

    • Conflictos: Si el CSS clásico usa selectores muy genéricos, podría haber conflictos de estilos con el CSS de tu aplicación React (especialmente si usas librerías como MUI, Tailwind, o CSS Modules que encapsulan los estilos). Asegurate de que los estilos sean específicos y no se pisen.
    • Encapsulamiento: Las soluciones de CSS-in-JS (como Emotion en MUI) o CSS Modules en React están diseñadas para evitar conflictos. El CSS clásico no tiene este beneficio inherente.
  5. Mantenimiento y Coherencia:

    • Dificultad de Mantenimiento: Tienes dos paradigmas de desarrollo diferentes en un mismo lugar, lo que puede dificultar el mantenimiento y la depuración.
    • Falta de Coherencia: La estética y el comportamiento pueden ser inconsistentes si no se manejan cuidadosamente, especialmente si mezclas estilos clásicos con librerías como Material UI.

Ejemplo de Integración Básica con useEffect y useRef:

JavaScript
import React, { useRef, useEffect } from 'react';
import { Head } from '@inertiajs/react'; // Si usas Inertia, útil para títulos de página

// Asumimos que tienes un archivo classic-component.js
// y un classic-component.css
// Estos deberían ser importados o incluidos en tu bundle principal,
// o cargados dinámicamente si son grandes.

// Contenido de classic-component.js (ejemplo)
/*
window.initClassicWidget = (elementId, initialData) => {
  const container = document.getElementById(elementId);
  if (!container) return;

  container.innerHTML = `<p>Widget clásico: ${initialData.message}</p>
                         <button id="classic-button-${elementId}">Haz clic en el Clásico</button>`;
  const button = document.getElementById(`classic-button-${elementId}`);
  let clickCount = 0;
  button.onclick = () => {
    clickCount++;
    console.log(`Botón clásico clicado ${clickCount} veces. Mensaje: ${initialData.message}`);
    // Ejemplo de cómo el clásico podría "hablar" con React (disparando un evento DOM)
    const event = new CustomEvent('classic-widget-clicked', { detail: { count: clickCount } });
    container.dispatchEvent(event);
  };
  console.log(`Widget clásico inicializado para ${elementId}`);

  return { // Devolvemos una función de limpieza si el widget tiene estado o listeners
    destroy: () => {
      console.log(`Widget clásico desmontado para ${elementId}`);
      button.onclick = null; // Eliminar event listener
      container.innerHTML = ''; // Limpiar contenido
    }
  };
};
*/

function ClassicComponentWrapper({ message }) {
  const containerRef = useRef(null);
  const widgetInstanceRef = useRef(null); // Para guardar la referencia al objeto del widget clásico

  useEffect(() => {
    // Asegurarse de que el elemento DOM exista
    if (containerRef.current && window.initClassicWidget) {
      console.log('Montando componente clásico...');
      // Inicializa tu componente JavaScript clásico aquí, pasándole el ID o la ref
      // y cualquier prop de React que necesite.
      widgetInstanceRef.current = window.initClassicWidget(containerRef.current.id, { message });

      // Escuchar eventos desde el widget clásico
      const handleClassicClick = (event) => {
        console.log('Evento desde widget clásico recibido en React:', event.detail.count);
        // Aquí podrías actualizar un estado de React si lo necesitaras
      };
      containerRef.current.addEventListener('classic-widget-clicked', handleClassicClick);


      // Función de limpieza
      return () => {
        console.log('Desmontando componente clásico...');
        // Llama a la función de "destrucción" de tu JS clásico si existe
        if (widgetInstanceRef.current && typeof widgetInstanceRef.current.destroy === 'function') {
          widgetInstanceRef.current.destroy();
        }
        // Remover el event listener
        if (containerRef.current) {
          containerRef.current.removeEventListener('classic-widget-clicked', handleClassicClick);
        }
      };
    }
  }, [message]); // El efecto se re-ejecuta si 'message' cambia, re-inicializando/actualizando el clásico

  return (
    <div id="classic-widget-container" ref={containerRef} style={{ border: '1px solid gray', padding: '10px', margin: '20px' }}>
      {/* El contenido inicial puede estar vacío o tener un loader, el JS clásico lo llenará */}
      Cargando componente clásico...
    </div>
  );
}

// --- Cómo usarlo en una página de Inertia/React ---
export default function MiPaginaConComponenteClasico({ initialMessage }) {
  return (
    <>
      <Head title="Página con Clásico" />
      <div>
        <h1>Mi Aplicación con Componente Clásico</h1>
        <p>Esta es una página de React/Inertia.</p>
        <ClassicComponentWrapper message={initialMessage || "Mensaje por defecto"} />
      </div>
    </>
  );
}

En conclusión, sí, es totalmente posible y a veces necesario. Solo requiere una planificación cuidadosa para la inicialización, la limpieza, la comunicación de estado y la gestión de estilos, asegurando que los dos paradigmas coexistan de manera armoniosa y sin introducir errores difíciles de depurar.

No hay comentarios: