React: createPortal: opción de soporte para detener la propagación de eventos en el árbol de React

Creado en 27 oct. 2017  ·  103Comentarios  ·  Fuente: facebook/react

¿Quieres solicitar una función o informar de un error ?
Característica, pero también un error que causa que la nueva API rompa la vieja unstable_rendersubtreeintocontainer

¿Cuál es el comportamiento actual?
No podemos detener la propagación de todos los eventos desde el portal a sus antepasados ​​del árbol React. Nuestro mecanismo de capas con modales / popovers completamente roto. Por ejemplo, tenemos un botón desplegable. Cuando hacemos clic en él, el clic abre una ventana emergente. También queremos cerrar este popover al hacer clic en el mismo botón. Con createPortal, haga clic dentro de popover, haga clic en el botón y se cerrará. Podemos usar stopPropagation en este simple caso. Pero tenemos muchos casos de este tipo, y necesitamos usar stopPropagation para todos ellos. Además, no podemos detener todos los eventos.

¿Cuál es el comportamiento esperado?
createPortal debería tener una opción para detener la propagación de eventos sintéticos a través del árbol de React sin detener manualmente cada evento. ¿Qué piensas?

DOM Feature Request

Comentario más útil

Incluso eso me parece innecesariamente complejo. ¿Por qué no simplemente agregar una bandera booleana opcional a createPortal permitiendo bloquear el comportamiento de burbujeo?

Todos 103 comentarios

Además, la propagación de mouseOver / Leave parece completamente inesperada.
image

¿Puedes mover el portal fuera del botón?

p.ej

return [
  <div key="main">
    <p>Hello! This is first step.</p>
    <Button key="button" />
  </div>,
  <Portal key="portal" />
];

Entonces no burbujeará a través del botón.

Fue lo primero que pensé, ¡pero!) Imagina que tenemos el controlador mouseEnter en dicho contenedor de componentes:

image

Con unstable_rendersubtreeintocontainer no necesito nada que ver con los eventos en el componente ButtonWithPopover - mouseEnter simplemente funciona cuando el mouse realmente ingresa div y el elemento DOM del botón, y no se activa cuando el mouse está sobre el popover. Con el portal, el evento se activa cuando se pasa el mouse sobre la ventana emergente, y en realidad NO sobre div en este momento. Entonces, necesito detener la propagación. Si lo hago en el componente ButtonWithPopover , interrumpiré la activación del evento cuando el mouse esté sobre el botón. Si lo hago en popover y estoy usando algún componente de popover común para esta aplicación, también puedo romper la lógica en otras partes de la aplicación.

Realmente no entiendo el propósito de burbujear a través del árbol de React. Si necesito eventos del componente del portal, simplemente puedo pasar controladores a través de accesorios. Lo hicimos con unstable_rendersubtreeintocontainer y funcionó perfectamente.

Si abro una ventana modal desde algún botón en lo profundo del árbol de reacción, recibiré un disparo inesperado de eventos en modal. stopPropagation también detendrá la propagación en DOM, y no obtendré eventos que realmente espero que se activen (

@gaearon Sugeriría que esto es más un error que una solicitud de función. Tenemos una serie de errores nuevos causados ​​por eventos del mouse que se propagan a través de los portales (donde anteriormente usábamos unstable_rendersubtreeintocontainer ). Algunos de estos no se pueden arreglar incluso con una capa div adicional para filtrar los eventos del mouse porque, por ejemplo, confiamos en que los eventos mousemove se propaguen hasta el documento para implementar diálogos arrastrables.

¿Hay alguna forma de solucionar este problema antes de que se aborde en una versión futura?

Creo que se le llama una solicitud de función, porque el comportamiento de burbuja actual de los portales es tanto esperado como previsto. El objetivo es que el subárbol actúe como un hijo real de sus padres.

Lo que sería útil son casos o situaciones de uso adicionales (como las que está viendo) que no cree que sean atendidas por la implementación actual o que sean difíciles de solucionar.

Entiendo que este comportamiento es intencionado, pero creo que es un error significativo que no se puede deshabilitar.

En mi opinión, la biblioteca que trabaja con DOM debería preservar el comportamiento de implementación de DOM, no romperlo.

Por ejemplo:

class Container extends React.Component {
  shouldComponentUpdate = () => false;
  render = () => (
    <div
      ref={this.props.containerRef}
      // Event propagation on this element not working
      onMouseEnter={() => { console.log('handle mouse enter'); }}
      onClick={() => { console.log('handle click'); }}
    />
  )
}

class Root extends React.PureComponent {
  state = { container: null };
  handleContainer = (container) => { this.setState({ container }); }

  render = () => (
    <div>
      <div
        // Event propagation on this element not working also
        onMouseEnter={() => { console.log('handle mouse enter'); }}
        onClick={() => { console.log('handle click'); }}
      >
        <Container containerRef={this.handleContainer} />
      </div>
      {this.state.container && ReactDOM.createPortal(
        <div>Portal</div>,
        this.state.container
      )}
    </div>
  );
}

Cuando trabajo con DOM, espero recibir eventos como lo hace la implementación de DOM. En mi ejemplo, los eventos se propagan a través de Portal , trabajando alrededor de sus padres DOM, y esto puede considerarse como un error .

Amigos, gracias por la discusión, sin embargo, no creo que sea tan útil discutir si algo es un error o no. En cambio, sería más productivo discutir los casos de uso y ejemplos que no se cumplen con el comportamiento actual, para que podamos comprender mejor si la forma actual es la mejor para el futuro.

En general, queremos que la API maneje un conjunto diverso de casos de uso y, con suerte, no limite demasiado a otros. No puedo hablar por el equipo central, pero me imagino que hacerlo configurable no es una solución probable. Generalmente, React se inclina por una API consistente sobre las configurables.

También entiendo que este comportamiento no es cómo funciona el DOM, pero no creo que eso sea en sí mismo una buena razón para decir que no debería ser así. Gran parte del comportamiento de react-dom es diferente de cómo funciona el DOM, es posible que los eventos ya sean diferentes de la versión nativa. onChange por ejemplo, es completamente diferente al evento de cambio nativo, y todos los eventos de reacción hacen burbujas independientemente del tipo, a diferencia del DOM.

En cambio, sería más productivo para discutir los casos de uso y ejemplos que no se cumplen con el comportamiento actual.

Aquí hay dos ejemplos que se nos han roto en nuestra migración a React 16.

Primero, tenemos un cuadro de diálogo que se puede arrastrar y que se inicia con un botón. Intenté agregar un elemento de "filtrado" en el uso de nuestro Portal que llamó StopPropagation en cualquier mouse * y key * eventos. Sin embargo, confiamos en poder vincular un evento mousemove al documento para implementar la funcionalidad de arrastre; esto es común porque si el usuario mueve el mouse a una velocidad significativa, el cursor sale de los límites del cuadro de diálogo y usted necesita para poder capturar el movimiento del mouse a un nivel superior. El filtrado de estos eventos rompe esta funcionalidad. Pero con los portales, los eventos del mouse y las teclas están surgiendo desde el interior del cuadro de diálogo hasta el botón que lo inició, lo que hace que muestre diferentes efectos visuales e incluso descarte el cuadro de diálogo. No creo que sea realista esperar que todos los componentes que se lanzarán a través de un Portal enlazarán entre 10 y 20 controladores de eventos para detener la propagación de este evento.

En segundo lugar, tenemos un menú contextual emergente que se puede iniciar con un clic del mouse primario o secundario. Uno de los consumidores internos de nuestra biblioteca tiene controladores de mouse adjuntos al elemento que inicia este menú y, por supuesto, el menú también tiene controladores de clic para manejar la selección de elementos. El menú ahora vuelve a aparecer con cada clic, ya que los eventos de mousedown / mousedown están volviendo a aparecer en el botón que inicia el menú.

No puedo hablar por el equipo central, pero me imagino que hacerlo configurable no es una solución probable. Generalmente, React se inclina por una API consistente sobre las configurables.

Les imploro (y al equipo) que reconsideren esta posición en este caso particular. Creo que el burbujeo de eventos será interesante para ciertos casos de uso (aunque no puedo pensar en ninguno). Pero creo que será paralizante en otros e introduce una inconsistencia significativa en la API. Si bien unstable_rendersubtreeintocontainer nunca fue súper compatible, era lo que todos solían renderizar fuera del árbol inmediato, y no funcionó de esta manera. Fue oficialmente desaprobado en favor de los portales, pero los portales rompen la funcionalidad de esta manera crítica y no parece haber una solución fácil. Creo que esto puede describirse con bastante razón como bastante inconsistente.

También entiendo que este comportamiento no es cómo funciona el DOM, pero no creo que eso sea en sí mismo una buena razón para decir que no debería ser así.

Entiendo de dónde vienes, pero creo que en este caso (a) es un comportamiento fundamental que (b) actualmente no tiene solución, así que creo que "el DOM no funciona de esta manera" es un argumento sólido, si no completamente convincente.

Y para ser claros: mi solicitud de que esto se considere un error es principalmente para que se priorice para una solución más temprano que tarde.

Mi modelo mental de un Portal es que se comporta como si estuviera en el mismo lugar en el árbol, pero evita problemas como "desbordamiento: oculto" y evita el desplazamiento con fines de dibujo / diseño.

Hay muchas soluciones "emergentes" similares que ocurren en línea sin un portal. Por ejemplo, un botón que expande un cuadro junto a él.

Tome como ejemplo el cuadro de diálogo "Elija su reacción" aquí en GitHub. Eso se implementa como un div justo al lado del botón. Eso funciona bien ahora. Sin embargo, si quiere tener un índice z diferente, o ser sacado de un área overflow: scroll que contiene estos comentarios, entonces deberá cambiar la posición del DOM. Ese cambio no es seguro a menos que también se conserven otras cosas, como la propagación de eventos.

Ambos estilos de "ventanas emergentes" o "ventanas emergentes" son legítimos. Entonces, ¿cómo resolvería el mismo problema cuando el componente está en línea en el diseño en lugar de flotar fuera de él?

La solución alternativa que funcionó para mí es llamar a stopPropagation directamente debajo de la representación del portal:

return createPortal(
      <div onClick={e => e.stopPropagation()}>{this.props.children}</div>,
      this.el
    )

Eso funciona muy bien para mí, ya que tengo un solo componente de abstracción que usa portales; de lo contrario, tendrá que arreglar todas sus llamadas createPortal .

@methyl, esto asume que conoce todos los eventos que necesita para mousemove para subir al documento, pero no para subir el árbol de renderizado.

Ambos estilos de "ventanas emergentes" o "ventanas emergentes" son legítimos. Entonces, ¿cómo resolvería el mismo problema cuando el componente está en línea en el diseño en lugar de flotar fuera de él?

@sebmarkbage No estoy seguro de que esta pregunta tenga sentido. Si tuviera este problema al insertar el componente, no lo integraría.

Creo que algunos de los problemas aquí son los casos de uso de renderSubtreeIntoContainer se están transfiriendo a createPortal cuando los dos métodos están haciendo cosas conceptualmente diferentes. Creo que el concepto de Portal se estaba sobrecargando.

Estoy de acuerdo en que en el caso del diálogo Modal, casi nunca quieres que el modal actúe como un hijo del botón que lo abrió. El componente disparador solo lo representa porque controla el estado open . Creo que es un error decir que la implementación del portal es incorrecta, en lugar de decir que createPortal en el botón no es la herramienta adecuada para esto. En este caso, el Modal no es un elemento secundario del disparador, y no debería representarse como si lo fuera. Una posible solución es seguir usando renderSubtreeIntoContainer , otra opción de usuario-land es tener un ModalProvider cerca de la raíz de la aplicación que maneja la representación de modales y transmite (a través del contexto) un método para representar un elemento modal arbitrario necesita la raíz

renderSubtreeIntoContainer no se puede llamar desde dentro de render o métodos de ciclo de vida en React 16, lo que prácticamente excluye su uso para los casos que he estado discutiendo (de hecho, todos nuestros componentes que fueron Al hacer esto, se rompió por completo en la migración a 16). Los portales son la recomendación oficial: https://reactjs.org/blog/2017/09/26/react-v16.0.html#breaking -changes

Estoy de acuerdo en que el concepto de Portales pudo haber terminado sobrecargado. Sin embargo, no estoy seguro de que me guste la solución de un componente global y su contexto. Parece que esto podría resolverse fácilmente con una bandera en createPortal que especifique si los eventos deben pasar. Sería una marca de suscripción que preservaría la compatibilidad de API con 16+.

Intentaré aclarar nuestro caso de uso de los portales y por qué nos encantaría ver una opción para detener la propagación de eventos. En la aplicación ManyChat, estamos usando portales para crear 'capas'. Tenemos el sistema de capas para toda la aplicación que utiliza varios tipos de componentes: popovers, desplegables, menús, modales. Cada capa puede exponer una nueva capa, por ejemplo, el botón en un segundo nivel del menú puede activar la ventana modal donde el otro botón puede abrir la ventana emergente. En la mayoría de los casos, la capa es la nueva rama de UX que resuelve su propia tarea. Y cuando se abre una nueva capa, el usuario debe interactuar con esta nueva capa, no con otras en la parte inferior. Entonces, para este sistema, hemos creado un componente común para renderizar en capas:

class RenderToLayer extends Component {
  ...
  stop = e => e.stopPropagation()

  render() {
    const { open, layerClassName, useLayerForClickAway, render: renderLayer } = this.props

    if (!open) { return null }

    return createPortal(
      <div
        ref={this.handleLayer}
        style={useLayerForClickAway ? clickAwayStyle : null}
        className={layerClassName}
        onClick={this.stop}
        onContextMenu={this.stop}
        onDoubleClick={this.stop}
        onDrag={this.stop}
        onDragEnd={this.stop}
        onDragEnter={this.stop}
        onDragExit={this.stop}
        onDragLeave={this.stop}
        onDragOver={this.stop}
        onDragStart={this.stop}
        onDrop={this.stop}
        onMouseDown={this.stop}
        onMouseEnter={this.stop}
        onMouseLeave={this.stop}
        onMouseMove={this.stop}
        onMouseOver={this.stop}
        onMouseOut={this.stop}
        onMouseUp={this.stop}

        onKeyDown={this.stop}
        onKeyPress={this.stop}
        onKeyUp={this.stop}

        onFocus={this.stop}
        onBlur={this.stop}

        onChange={this.stop}
        onInput={this.stop}
        onInvalid={this.stop}
        onSubmit={this.stop}
      >
        {renderLayer()}
      </div>, document.body)
  }
  ...
}

Este componente detiene la propagación para todos los tipos de eventos de los documentos de React y nos permitió actualizar a React 16.

¿Esto necesita estar vinculado a portales? En lugar de portales de espacio aislado, ¿qué pasaría si solo hubiera (por ejemplo) <React.Sandbox>...</React.Sandbox> ?

Incluso eso me parece innecesariamente complejo. ¿Por qué no simplemente agregar una bandera booleana opcional a createPortal permitiendo bloquear el comportamiento de burbujeo?

@gaearon, esta es una situación bastante desafortunada para una parte de nosotros, ¿podrías tú o alguien querido echarle un vistazo a esto? :)

Agregaría que mi pensamiento actual es que ambos casos de uso deben ser compatibles. Realmente hay casos de uso en los que necesita que el contexto fluya desde el padre actual al subárbol, pero que ese subárbol no actúe como un hijo lógico en términos del DOM. Los modales complejos son el mejor ejemplo, casi nunca desea que los eventos de un formulario en una ventana modal se propaguen hasta el botón de activación, pero es casi seguro que necesite que se pase el contexto (i18n, temas, etc.)

Diré que ese caso de uso _ podría_ resolverse principalmente con un ModalProvider más cerca de la raíz de la aplicación que se procesa a través de createPortal lo suficientemente alto como para que la propagación del evento no afecte nada, pero eso comienza a parecer una solución alternativa en lugar de una Arquitectura bien diseñada. También hace que los modales proporcionados por la biblioteca sean más molestos para los usuarios, ya que ya no son autónomos.

Yo agregaría aunque en términos de API, no creo que createPortal deba hacer ambas cosas, el caso modal realmente quiere usar ReactDOM.render (skool antiguo) porque está bastante cerca de un árbol distinto _excepto_ que la propagación del contexto a menudo es necesaria

Solo tuvimos que arreglar un error extremadamente difícil de diagnosticar en el código de administración de enfoque de nuestra aplicación externa como resultado de usar la solución que @ kib357 publicó.

Específicamente: llamar a stopPropagation en el evento de enfoque sintético para evitar que salga del portal hace que también se llame a stopPropagation en el evento de enfoque nativo en el controlador capturado de React en #document, lo que significa que no llegó a otro controlador capturado en <body> . Lo arreglamos moviendo nuestro controlador a #document, pero habíamos evitado específicamente hacerlo en el pasado para no pisar los dedos de React.

El nuevo comportamiento burbujeante en Portals realmente me parece un caso minoritario. Sea esa opinión o la verdad, ¿podríamos conseguir algo de tracción sobre este tema? ¿Quizás @gaearon? Tiene cuatro meses y causa un dolor real. Creo que esto podría describirse como un error dado que es un cambio de API importante en React 16 sin una solución completamente segura.

@craigkovatch Todavía tengo curiosidad por saber cómo resolverías mi ejemplo en línea. Digamos que la ventana emergente está reduciendo el tamaño del cuadro. Insertar algo es importante, ya que está empujando algo hacia abajo en el diseño dado su tamaño. No puede simplemente pasar el cursor por encima.

Potencialmente, podría medir el popover, insertar un marcador de posición en blanco con el mismo tamaño e intentar alinearlo en la parte superior, pero eso no es lo que la gente hace.

Entonces, si su ventana emergente necesita expandir el contenido en su lugar, como justo al lado del botón, ¿cómo lo resolvería? Sospecho que el patrón que funciona allí funcionará en ambos casos y deberíamos recomendar el único patrón.

Creo que, en general, este es el patrón que funciona en ambos escenarios:

class Foo extends React.Component {
  state = {
    highlight: false,
    showFlyout: false,
  };

  mouseEnter() {
    this.setState({ highlight: true });
  }

  mouseLeave() {
    this.setState({ highlight: false });
  }

  showFlyout() {
    this.setState({ showFlyout: true });
  }

  hideFlyout() {
    this.setState({ showFlyout: false });
  }

  render() {
    return <>
      <div onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} className={this.state.highlight ? 'highlight' : null}>
        Hello
        <Button onClick={this.showFlyout} />
      </div>
      {this.state.showFlyout ? <Flyout onHide={this.hideFlyout} /> : null}
    </>;
  }
}

Si el Flyout es un portal, entonces funciona y no obtiene ningún evento de mouse al pasar el mouse sobre el portal. Pero lo que es más importante, también funciona si NO es un portal y debe ser un control flotante en línea. No se necesita stopPropagation.

Entonces, ¿qué tiene este patrón que no funciona para su caso de uso?

@sebmarkbage estamos usando portales de una manera completamente diferente, renderizándolos en un contenedor montado como el hijo final de <body> que luego se coloca, a veces con un índice z. La documentación de React sugiere que esto está más cerca de la intención del diseño; es decir, renderizar en un lugar totalmente diferente en el DOM. No me parece que nuestros casos de uso sean lo suficientemente similares como para que la discusión pertenezca a este hilo. Pero si quieren intercambiar ideas / solucionar problemas juntos, estaré más que feliz de discutir más en otro foro.

No, mi caso de uso es ambos . A veces uno y otras veces el otro. Por eso es relevante.

El <Flyout /> puede optar por renderizar en el hijo final de body o no, pero siempre que levante el portal a un hermano del componente flotante en lugar de a un hijo de él, su escenario funciona.

Creo que hay un escenario plausible en el que eso es inconveniente y desea una forma de teletransportar cosas desde componentes profundamente anidados, pero en ese escenario probablemente esté bien con el contexto como el contexto desde el punto intermedio. Pero creo que esos son dos temas separados.

Quizás necesitemos una API de tragamonedas para eso.

class Foo extends React.Component {
  state = {
    showFlyout: false,
  };

  showFlyout() {
    this.setState({ showFlyout: true });
  }

  hideFlyout() {
    this.setState({ showFlyout: false });
  }

  render() {
    return <>
      Hello
      <Button onClick={this.showFlyout} />
      <SlotContent name="flyout">
        {this.state.showFlyout ? <Flyout onHide={this.hideFlyout} /> : null}
      </SlotContent>
    </>;
  }
}

class Bar extends React.Component {
  state = {
    highlight: false,
  };

  mouseEnter() {
    this.setState({ highlight: true });
  }

  mouseLeave() {
    this.setState({ highlight: false });
  }

  render() {
    return <>
      <div onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} className={this.state.highlight ? 'highlight' : null}>
        <SomeContext>
          <DeepComponent />
        </SomeContext>
      </div>
      <Slot name="flyout" />
    </>;
  }
}

El portal obtendría el contexto de Bar, no de DeepComponent. El contexto y la propagación de eventos siguen compartiendo la misma ruta de árbol.

@sebmarkbage, el caso modal generalmente requiere contexto desde el punto en el que se representa. Creo que es un caso ligeramente único, el componente es un hijo lógico de la cosa que lo representó pero _no_ estructural (a falta de una palabra mejor), por ejemplo, por lo general, desea cosas como el contexto de la forma (relé, formik, forma redux , lo que sea) pero no eventos DOM por los que pasar. Uno también termina renderizando esos modales bastante profundos en los árboles, junto a sus desencadenantes, por lo que permanecen componentes y son reutilizables, más que porque pertenecen allí estructuralmente.

Creo que este caso es generalmente diferente al caso desplegable / desplegable que sirve createPortal. Tbc Creo que el comportamiento burbujeante de los portales es bueno, pero no para los modales. También creo que esto podría manejarse con Context y algún tipo de ModalProvider razonablemente bien, pero eso es un poco molesto, especialmente para las bibliotecas.

Siempre que levante el portal a un hermano del componente flotante en lugar de a un hijo del mismo, su escenario funcionará.

No estoy seguro de seguir. Todavía existe el problema de que, por ejemplo, los eventos keyDown se propaguen a través de un árbol DOM inesperado.

@jquense Tenga en cuenta que en mi ejemplo, la ranura todavía está dentro del componente Bar, por lo que obtendría su contexto del formulario en algo como <Form><Bar /></Form> .

Incluso si el portal se convirtió en el cuerpo del documento.

Entonces es como dos indirecciones (portales): profundo -> hermano de Bar -> cuerpo del documento.

Entonces, el contexto del portal sigue siendo el contexto del formulario, y también lo es la cadena de burbujeo de eventos, pero ninguno está en el contexto de la cosa flotante.

Sí, lo siento, me perdí eso 😳 Si lo estoy leyendo bien, ¿todavía tendrías el burbujeo en <Slot> arriba? Eso es definitivamente mejor, aunque creo que en el caso del diálogo modal probablemente uno no quiere que _cualquier_ burbujee. Como si pensara en términos de un lector de pantalla, desea que todo lo que esté fuera del modal se invierta, mientras está activo. No lo sé, creo que en ese caso el burbujeo es un problema, nadie esperaría que un clic dentro de un cuadro de diálogo fluyera a cualquier parte.

Quizás el problema aquí no son los portales, pero ¿no hay una buena manera de compartir el contexto entre árboles? Una parte del contexto ReactDOM.render está muy bien para los modales, y tal vez sea una forma más "correcta" de pensar al respecto de todos modos ...

Mi pensamiento aquí es que hay algo de burbujeo porque todavía va del modal al div al cuerpo al documento a la ventana. Y conceptualmente más allá del marco hacia la ventana contenedora y así sucesivamente.

Eso no es teórico en algo como el contenido renderizado ART o GL (y hasta cierto punto React Native) donde podría no haber un árbol de respaldo existente para obtener esa semántica. Así que tiene que haber una forma de decir que aquí es donde burbujea.

En algunas aplicaciones hay modales en modales. Por ejemplo, en FB hay una ventana de chat que puede estar por encima de un modal o un modal puede ser parte de la ventana de chat. Entonces, incluso un modal tiene algún contexto en cuanto a a qué parte del árbol pertenece. Nunca es completamente independiente.

Eso no quiere decir que no podamos tener dos semánticas diferentes para el burbujeo de eventos y el contexto. Ambos son explícitos sobre esto y puedes portal uno sin el otro, etc.

Sin embargo, tener la garantía de que ambos siguen el mismo camino es realmente poderoso, ya que significa que el burbujeo de eventos se puede implementar por completo para los eventos del espacio del usuario de la misma manera que en el navegador.

Por ejemplo, esto sucede con varios contextos de Redux en la actualidad. Imagina que this.context.dispatch("Hover") es un evento de espacio de usuario que se propaga. Incluso podríamos implementar eventos React como parte del contexto. Parece razonable pensar que puedo usar esto de la misma manera, y en todos los sentidos ahora mismo, tú puedes. Creo que si bifuráramos estos dos contextos, probablemente terminaríamos con otra API de contexto de espacio de usuario que sigue la estructura DOM en paralelo con el contexto normal, si son realmente tan diferentes.

Así que esa es la razón por la que estoy presionando un poco para ver si lo de las tragamonedas podría ser suficiente, ya que a) debes ser explícito sobre qué contexto ocurre de todos modos. b) puede evitar bifurcar el mundo y tener dos sistemas de contexto completos.

Específicamente: llamar a stopPropagation en el evento de enfoque sintético para evitar que salga del portal hace que también se llame a stopPropagation en el evento de enfoque nativo en el controlador capturado de React en #document, lo que significa que no llegó a otro controlador capturado en

. Lo arreglamos moviendo nuestro controlador a #document, pero habíamos evitado específicamente hacerlo en el pasado para no pisar los dedos de React.

@craigkovatch , ¿ onFocusCapture en el documento? En mi solución alternativa, los eventos capturados no deberían detenerse. ¿Puede proporcionar un ejemplo más detallado de cómo fue y qué ha hecho para resolver su problema?
Además, creo que mi código tiene un problema al detener el evento blur ; no debería detenerse. Por lo tanto, investigaré esta pregunta más a fondo e intentaré encontrar una solución más confiable.

@ kib357 No estoy sugiriendo que haya un problema en su solución alternativa, creo que hay un error separado en React allí (es decir, no debería cancelar la propagación de eventos de enfoque nativo en la fase de captura al llamar a stopPropagation en eventos de enfoque sintético en la fase de burbujeo).

El código en cuestión utiliza un detector de eventos de captura nativo, es decir, document.body.addEventListener('focus', handler, true)

@craigkovatch suena interesante, dado que usó el controlador capturado. Sin embargo, no tengo idea de por qué sucede esto.

Entonces, muchachos, tenemos dos escenarios diferentes para usar la representación del portal:

  1. Para evitar problemas de CSS como desbordamiento: oculto y etc. en widgets simples, como botones desplegables o menús de un nivel
  2. Para crear una nueva capa de UX para casos más poderosos como:
  3. modales
  4. menús anidados
  5. popovers-with-forms-with-dropdowns -... - todos los casos, cuando se combinan capas

Creo que la API actual createPortal satisface solo el primer escenario. La sugerencia de usar un nuevo React.render por segundo es inutilizable; es muy deficiente crear una aplicación separada con todos los proveedores para cada capa.
¿Qué información adicional podemos proporcionar para ayudar a resolver este problema?
¿Qué desventajas del parámetro sugerido en la API createPortal ?

@sebmarkbage Mi pregunta inmediata con la API de tragamonedas es: ¿podría insertar varios SlotContents en uno Slot al mismo tiempo? No es raro en nuestra interfaz tener múltiples "ventanas emergentes" o "modales" abiertos simultáneamente. En mi mundo perfecto, una Popup API se vería así:

import { App } from './app'
import { PopupSlot } from './popups'

let root = (
  <div>
    <App />
    <PopupSlot />
  </div>
)

ReactDOM.render(root, document.querySelector('#root'))

// some dark corner of our app

import { Popup } from './popups'

export function SoManyPopups () {
  return <>
    <Popup>My Entire</Popup>
    <Popup>Interface</Popup>
    <Popup>Is Popups</Popup>
  </>
}

Tenemos un nuevo problema con esto para el que no he podido encontrar una solución alternativa. Usando el enfoque de "trampa de eventos" sugerido anteriormente, solo los eventos de React Synthetic están bloqueados para que no salgan del portal. Los eventos nativos aún burbujean, y dado que nuestro código React está alojado dentro de una aplicación mayoritariamente jQuery, el controlador global jQuery keyDown en <body> aún obtiene el evento.

Intenté agregar un oyente event.stopPropagation al elemento contenedor nativo dentro del Portal a través de una referencia como esta, pero esto neutraliza por completo todos los eventos sintéticos dentro del portal; asumí incorrectamente que el oyente de nivel superior de React estaba viendo la fase de captura.

No estoy seguro de qué se puede hacer aquí, aparte de los cambios en React.

const allTheEvents: string[] = 'click contextmenu doubleclick drag dragend dragenter dragexit dragleave dragover dragstart drop mousedown mouseenter mouseleave mousemove mouseover mouseout mouseup keydown keypress keyup focus blur change input invalid submit'.split(' ');
const stop = (e: React.SyntheticEvent<HTMLElement>): void => { e.stopPropagation(); };
const nativeStop = (e: Event): void => e.stopPropagation();
const handleRef = (ref: HTMLDivElement | null): void => {
  if (!ref) { return; }
  allTheEvents.forEach(eventName => ref.addEventListener(eventName, nativeStop));
};


/** Prevents https://reactjs.org/docs/portals.html#event-bubbling-through-portals */
export function PortalEventTrap(children: React.ReactNode): JSX.Element {
  return <div
      onClick={stop}
      ...

      ref={handleRef}
    >
      {children}
    </div>;
}

Eso depende del orden en que se inicialicen ReactDOM y JQuery. Si JQuery se inicializa primero, los controladores de eventos de nivel superior de JQuery se instalarán primero y, por lo tanto, se ejecutarán antes de que se ejecuten los controladores sintéticos de ReactDOM.

Tanto ReactDOM como JQuery prefieren tener solo un oyente de nivel superior que luego simule el burbujeo internamente, a menos que haya algún evento en el que el navegador no burbujee, como scroll .

@Kovensky, tenía entendido que jQuery no hacía "burbujeo sintético" como lo hace React y, por lo tanto, no tiene un solo oyente de nivel superior. Mi inspector DOM tampoco revela ninguno. Me encantaría ver a qué estás haciendo referencia si me equivoco.

Ese será el caso de los eventos delegados. Por ejemplo, $(document.body).on('click', '.my-selector', e => e.stopPropagation()) .

Mire, esto se puede resolver en React, si alguien me convence de que esto no se puede resolver con mi diseño propuesto anteriormente, lo que requiere una reestructuración de su código. Pero no he visto ninguna razón por la que no se pueda hacer más que simplemente tratar de encontrar una solución rápida.

@sebmarkbage tu propuesta solo resuelve el caso de que los eventos se propaguen al propietario inmediato. ¿Qué pasa con el resto del árbol?

Aquí hay un caso de uso que creo que no se puede resolver bien con Slots o createPortal

<Form defaultValue={fromValue}>
   <more-fancy-markup />
   <div>
     <Field name="faz"/>
     <ComplexFieldModal>
       <Field name="foo.bar"/>
       <Field name="foo.baz"/>
     </ComplexFieldModal>
  </div>
</Form>

Y aquí hay un gif con una configuración similar pero ligeramente diferente, donde estoy usando createPortal para un sitio receptivo, para mover un campo de formulario a la barra de herramientas de la aplicación (mucho más arriba en el árbol). En este caso también, realmente no quiero que los eventos vuelvan a aparecer en el contenido de la página, pero definitivamente quiero que el contexto del formulario lo acompañe. Mi implementación, por cierto, es algo similar a Slot-esque usando el contexto ...

large gif 640x320

@sebmarkbage unstable_renderSubtreeIntoContainer permitió el acceso directo a la parte superior de la jerarquía independientemente de la posición de un componente, ya sea dentro de la jerarquía o como parte de un marco empaquetado separado.

Comparativamente, veo algunos problemas con la solución Slot:

  • La solución asume que tiene acceso a la posición de la jerarquía donde está "bien" para burbujear eventos. Este definitivamente no es el caso de componentes y marcos de componentes.
  • Asume que está "bien" hacer burbujas de eventos en cualquier otro nivel de la jerarquía.
  • Los eventos seguirán surgiendo de la posición de Slot. (como mencionó @craigkovatch )

También tengo un caso de uso (probablemente similar a los ya mencionados).

Tengo una superficie donde los usuarios pueden seleccionar cosas con su mouse con un "lazo". Esto es básicamente 100% de ancho / alto y está en la raíz de mi aplicación y usa el evento onMouseDown . En esta superficie también hay botones que abren portales como modales y menús desplegables. Un evento mouseDown dentro del portal es interceptado por el componente de selección de lazo en la raíz de la aplicación.

Veo muchos para solucionar el problema:

  • representar el portal un paso por encima del componente de lazo raíz, pero esto no es muy conveniente y probablemente necesitaría recurrir a una biblioteca basada en contexto como react-gateway? (o tal vez el sistema de tragamonedas mencionado).
  • detener la propagación manualmente dentro de la raíz del portal, pero podría provocar los efectos secundarios no deseados mencionados anteriormente
  • capacidad para detener la propagación en los portales de React (+1 por cierto)
  • filtrar eventos cuando provienen de un portal

Por ahora, mi solución es filtrar los eventos.

const appRootNode = document.getElementById('root');

const isInPortal = element => isNodeInParent(element, appRootNode);


    handleMouseDown = e => {
      if (!isInPortal(e.target)) {
        return;
      }
      ...
    };

Claramente, esta no será la mejor solución para todos nosotros y no será muy agradable si tiene portales anidados, pero para mi caso de uso actual (que es el único actualmente) funciona. No quiero agregar una nueva biblioteca de contexto o hacer una refactorización compleja para resolver esto. Solo quería compartir mi solución.

Pude lograr el bloqueo de eventos burbujeantes como se señaló en otra parte de este hilo.

Pero otro problema aparentemente más espinoso con el que me estoy encontrando es el onMouseEnter SyntheticEvent, que no aparece. Más bien, atraviesa desde el padre común del componente from componente to como se describe aquí . Esto significa que si el puntero del mouse ingresa desde fuera de la ventana del navegador, cada onMouseEnter manejador desde la parte superior del DOM hasta el componente en createPortal se activará en ese orden, lo que provocará que se activen todo tipo de eventos. nunca lo hice con unstable_renderSubtreeIntoContainer . Dado que onMouseEnter no burbujea, no se puede bloquear en el nivel del Portal. (Esto no pareció ser un problema con unstable_renderSubtreeIntoContainer ya que el evento onMouseEnter no respetaba la jerarquía virtual y no se secuenciaba a través del contenido del cuerpo, sino que descendía directamente al subárbol).

Si alguien tiene alguna idea sobre cómo evitar que los eventos onMouseEnter se propaguen desde la parte superior de la jerarquía DOM o se desvíen directamente al subárbol del portal, hágamelo saber.

@JasonGore También he notado este comportamiento.

Por ejemplo.

Tengo un menú contextual que se representa cuando un div se activa enMouseOver, luego abro un Modal con createPortal haciendo clic en uno de los elementos del menú. Cuando saco el mouse de la ventana del navegador, el evento onMouseLeave se propaga hasta el menú contextual, cerrando el menú contextual (y por lo tanto el Modal) ...

Tuve el mismo problema en el que tenía un elemento de lista en el que quería que se pudiera hacer clic en la totalidad (como un enlace), pero quería un botón de eliminar en las etiquetas debajo del nombre que abriría un modal para confirmar.

screenshot 2018-10-31 at 11 42 47

Mi única solución fue evitar el burbujeo en el div modal así:

// components/Modal.js

onClick(e) {
    e.stopPropagation();
}

return createPortal(
        <div onClick={this.onClick} ...
            ...

Evitará el burbujeo en cada modal, sí, pero no tengo ningún caso en el que me gustaría que eso sucediera todavía, así que funciona para mí.

¿Hay problemas potenciales con este enfoque?

@jnsandrew , no olvides que hay ~ 50 otros tipos de eventos que burbujean 🙃

Solo dale a esto. Me parece incómodo que React se comporte a su manera que es diferente al burbujeo de eventos DOM.

+1 a esto. Estamos usando React.createPortal para renderizar dentro del iframe, (tanto para estilos como para el aislamiento de eventos) y no poder evitar que los eventos salgan de la caja es un fastidio.

Parece que este es el duodécimo problema más criticado en la cartera de pedidos de React. Al menos los documentos son abiertos al respecto https://reactjs.org/docs/portals.html#event -bubbling-through-portals - aunque no mencionan las desventajas ni las soluciones y, en cambio, señalan que permite "abstracciones más flexibles ":

Los documentos deberían al menos explicar que esto puede causar problemas y sugerir soluciones. En mi caso, es un caso de uso bastante sencillo, usando https://github.com/reactjs/react-modal : tengo botones que abren cosas como menús desplegables, y dentro de estos hay botones que crean modales. Hacer clic en las burbujas modales hasta el botón superior, provocando que haga cosas no deseadas. Los modales se encapsulan en un componente cohesivo y al extraer la parte portaled se rompe esa encapsulación, creando una abstracción con fugas. Una solución alternativa podría ser mover una bandera para deshabilitar estos botones mientras el modal está abierto. Y, por supuesto, también puedo detener la propagación como se sugirió anteriormente, aunque en algunos casos es posible que no quiera hacer eso.

No estoy seguro de cuán útiles son el burbujeo y la captura en general (aunque sé que React se basa en burbujear debajo del capó); ciertamente tienen un historial histórico, pero prefiero pasar una devolución de llamada o propagar un evento más específico (por ejemplo, redux) que burbujear hacia arriba o capturar hacia abajo, ya que tales cosas probablemente a través de un montón de intermediarios innecesarios. Hay artículos como https://css-tricks.com/dangers-stopping-event-propagation/ y trabajo en aplicaciones que dependen de la propagación al cuerpo, principalmente para cerrar cosas al hacer clic en "afuera", pero prefiero coloque una superposición invisible sobre todo y cierre al hacer clic en eso. Por supuesto, no pude usar el Portal de React para crear una superposición tan invisible ...

También hay una pesadilla de mantenimiento aquí: a medida que se agregan nuevos eventos al DOM, cualquier portal "sellado" con la técnica discutida anteriormente "filtrará" esos nuevos eventos hasta que los mantenedores puedan agregarlos a la (extensa) lista negra.

Aquí hay un problema de diseño importante que debe abordarse. La capacidad de optar por participar o no participar en el burbujeo entre portales todavía me parece la mejor opción de API. No estoy seguro acerca de la dificultad de implementación, pero seguimos teniendo errores de producción relacionados con esto en Tableau, más de un año después.

Dedique 2 horas a intentar averiguar por qué mi formulario de modal estaba enviando otro formulario.
¡Finalmente lo descubrí gracias a ese problema!

Realmente me cuesta ver cuándo puede ser necesaria la propagación onSubmit . Lo más probable es que siempre se parezca más a un error que a una característica.

Al menos vale la pena agregar información de advertencia para reaccionar los documentos . Algo como esto:
Si bien la propagación de eventos a través de portales es una gran característica, algunas veces es posible que desee evitar la propagación de eventos. Puede lograrlo agregando onSubmit={(e) => {e.stopPropagation()}}

+1 a esto también. Estamos usando draftjs de manera pesada con texto en el que se puede hacer clic que muestra modales. Y todos los eventos en modal como focus, select, change, keypress, etc.simplemente explotan draftjs con errores.

En mi opinión, el comportamiento de proxy de eventos está fundamentalmente roto (y también me está causando errores), pero reconozco que esto es controvertido. Este hilo sugiere con bastante fuerza que existe la necesidad de un portal que incluya el contexto de los agujeros de gusano, pero no los eventos . ¿Está de acuerdo el equipo central? De cualquier manera, ¿cuál es el siguiente paso aquí?

Realmente no puedo darme cuenta, ¿por qué la propagación de eventos desde el portal es un comportamiento previsto? Esto está completamente en contra de la idea principal de propagación. Pensé que los portales se crearon exactamente para evitar este tipo de cosas (como anidamiento manual, propagación de eventos, etc.).

Puedo confirmar que si pones el portal cerca del árbol de elementos, se propagarán eventos:

class SomeComponent extends React.Component<any, any> {
  render() {
    return <>
      <div className="some-tree">
        // Portal here will bubble events
      </div>
      // Portal here will also bubble events, just checked
    </>
  }
}

+1 para esta solicitud de función

En DOM, los eventos aumentan en el árbol DOM. En React, los eventos aumentan en el árbol de componentes.

Confío bastante en el comportamiento existente, un ejemplo de lo cual son las ventanas emergentes que pueden estar anidadas; todos son portales para evitar problemas con overflow: hidden , pero para que la ventana emergente se comporte correctamente, necesito detectar clics externos en el componente de ventana emergente (que es diferente a detectar clics fuera de los elementos DOM renderizados) . Puede haber mejores ejemplos.

Creo que la discusión sólida aquí ha dejado en claro que hay buenas razones para tener ambos comportamientos. Debido a que createPortal representa un componente de React dentro de un nodo contenedor "DOM simple", no creo que sea factible que los Eventos sintéticos de React se propaguen desde un Portal hasta el árbol DOM simple.

Dado que los portales han estado disponibles durante mucho tiempo, probablemente sea demasiado tarde para cambiar el comportamiento predeterminado a "no propagarse más allá del límite del portal".

Basándome en toda la discusión hasta ahora, mi propuesta más simple es entonces (todavía): agregar una marca opcional a createPortal que evite cualquier propagación de eventos más allá del límite del portal.

Algo más sólido podría ser la capacidad de proporcionar una lista blanca de eventos a los que se les debería permitir "atravesar" el límite, mientras se detiene el resto.

@gaearon ¿Estamos en el punto en el que el equipo de React podría asumir esto? Este es un tema de los 10 principales, pero no hemos escuchado nada de ustedes sobre esto en bastante tiempo.

Quiero agregar mi apoyo a esto, y no estoy de acuerdo con los comentarios de

La capacidad de portal de contexto de un lugar en el DOM a otro es útil para implementar todo tipo de superposiciones, como información sobre herramientas, menús desplegables, tarjetas flotantes y diálogos, donde el contenido de la superposición se describe y se representa en el contexto de, el gatillo. Dado que el contexto es un concepto de React, este mecanismo resuelve un problema de React. Por otro lado, la capacidad de portal de eventos DOM que se propagan de un lugar en el DOM a otro es un truco elegante que le permite pretender que la estructura del DOM es diferente a la que usted configuró explícitamente. Esto resuelve un problema con el uso de la propagación de eventos DOM para la delegación, cuando desea delegar a una parte diferente del DOM. Probablemente debería usar devoluciones de llamada (o contexto) de todos modos, si tiene React, en lugar de confiar en los eventos DOM que se propagan desde adentro hacia afuera de la superposición. Como han señalado otros, rara vez desea "alcanzar" y manejar un evento que ocurre dentro de la superposición, de forma intencionada o no.

La propagación de eventos DOM resuelve principalmente el problema de hacer coincidir eventos DOM con destinos DOM. Cada clic es en realidad un clic en un conjunto completo de elementos anidados. No es mejor considerarlo como un mecanismo de delegación de alto nivel, en mi opinión, y el uso de eventos DOM para delegar a través de los límites de los componentes de React no es una gran encapsulación, a menos que los componentes sean pequeños componentes auxiliares privados que se utilizan para generar bits predecibles de DOM.

event.target === event.currentTarget me ayuda a resolver este problema. Pero esto es realmente un dolor de cabeza.

Esto me mordió hoy mientras intentaba migrar un componente popover usando unstable_renderSubtreeIntoContainer para usar createPortal . El componente en cuestión contiene elementos que se pueden arrastrar y se representa como un descendiente de otro elemento que se puede arrastrar. Esto significa que tanto el elemento principal como el elemento emergente contienen controladores de eventos táctiles y del mouse, que comenzaron a activarse al interactuar con el elemento emergente del portal.

Dado que unstable_renderSubtreeIntoContainer está en desuso (?), Es necesaria una solución alternativa; ninguna de las soluciones alternativas presentadas anteriormente parece ser una solución viable a largo plazo.

¡Oye! ¡Gracias por todas estas sugerencias chicos!
Me ayudó a solucionar uno de mis problemas.
¿Le gustaría leer un artículo excelente e informativo sobre la importancia y las habilidades del equipo React ? Supongo que será útil para todos los que estén interesados ​​en el desarrollo. ¡Buena suerte!

En mi opinión, es más frecuente que desee un portal que le brinde acceso al contexto, pero no a eventos emergentes. Cuando usamos Angular 1.x, escribimos nuestro propio servicio emergente que tomaría $scope y una cadena de plantilla, y compilaría / renderizaría esa plantilla y la agregaría al cuerpo. Implementamos todas las ventanas emergentes / modales / menús desplegables de nuestra aplicación con ese servicio, y ni una sola vez nos perdimos la falta de propagación de eventos.

La solución alternativa stopPropagation() parece evitar que los oyentes de eventos nativos en window activen (en nuestro caso, agregado por react-dnd-html5-backend ).

Aquí hay una reproducción mínima del problema: https://codepen.io/mogel/pen/xxKRPbQ

Si no hay un plan para proporcionar una forma de evitar el burbujeo sintético a través de los portales, ¿quizás alguien tenga una solución que no rompa el burbujeo de eventos nativos?

La solución alternativa stopPropagation () parece evitar que los oyentes de eventos nativos en la ventana se activen

Correcto. :(

Si no hay un plan para proporcionar una forma de evitar el burbujeo sintético a través de los portales

A pesar del silencio del equipo central, yo, y muchos otros en este hilo, _ realmente espero_ que existan tales planes.

¿Quizás alguien tiene una solución que no interrumpe el burbujeo de eventos nativos?

La solución alternativa de mi equipo ha sido prohibir los portales por completo debido a este problema evidente. Presentamos paneles con un gancho en un contenedor que vive dentro de los otros contextos de la aplicación, para que obtenga contextos de nivel raíz de forma gratuita; cualquier otro que pasemos manualmente. No es genial, pero es mejor que los inútiles controladores de eventos whack-a-mole.

Han pasado 17 meses desde la última respuesta de alguien del equipo central. Tal vez un ping podría llamar la atención sobre este problema :) @sebmarkbage o @gaearon

La solución alternativa de mi equipo ha sido prohibir los portales por completo debido a este problema evidente. Presentamos paneles con un gancho en un contenedor que vive dentro de los otros contextos de la aplicación, para que obtenga contextos de nivel raíz de forma gratuita; cualquier otro que pasemos manualmente. No es genial, pero es mejor que los inútiles controladores de eventos whack-a-mole.

No puedo pensar en ningún enfoque genérico para pasar contexto al "portal falso" a través de accesorios sin volver a depender de accesorios en cascada :(

Innumerables fueron los errores que detecté en https://github.com/reakit/reakit que estaban relacionados con este problema. Uso mucho React Portal y no puedo pensar en un solo caso en el que quisiera que los eventos se propaguen desde los portales a sus componentes principales.

Mi solución ha sido comprobarlo dentro de mis controladores de eventos principales:

event.currentTarget.contains(event.target);

O usando eventos nativos en su lugar:

const onClick = () => {};
React.useEffect(() => {
  ref.current.addEventListener("click", onClick);
  return () => ref.current.removeEventListener("click", onClick);
});

Utilizo esos enfoques internamente en la biblioteca. Pero ninguno de ellos es ideal. Y, dado que esta es una biblioteca de componentes de código abierto, no puedo controlar cómo las personas pasan los controladores de eventos a los componentes.

Una opción para deshabilitar la propagación de eventos resolvería todos esos problemas.

Hackeé una semi-solución que bloquea el burbujeo de React y al mismo tiempo reactiva un clon del evento en window . Parece funcionar en Chrome, Firefox y Safari en OSX, pero IE11 se deja fuera debido a que no permite que event.target se configure manualmente. Hasta ahora, solo se preocupa por los eventos Mouse, Pointer, Keyboard y Wheel. No estoy seguro de si los eventos de arrastre serían posibles de clonar.

Desafortunadamente, no se puede utilizar en nuestra base de código, ya que necesitamos compatibilidad con IE11, pero tal vez alguien más pueda adaptarlo para su propio uso.

Lo que hace que esto sea especialmente alucinante es que el comportamiento 'predeterminado' burbujea _hacia abajo_ el árbol de componentes nuevamente. Toma el siguiente árbol:

<Link>
   <Menu (portal)>
      <form onSubmit={...}>
         <button type="submit">

Me he estado tirando de los pelos durante horas, en cuanto a por qué con esta combinación exacta de componentes nunca se llama al onSubmit de mi formulario, independientemente de si hago clic en el botón Enviar o presiono Enter en un campo de entrada dentro del formulario.

Finalmente, descubrí que se debe a que el componente React Router Link tiene una implementación onClick que hace e.preventDefault() para evitar que el navegador se recargue. Sin embargo, esto tiene el desafortunado efecto secundario de bloquear también el comportamiento predeterminado del clic en el botón enviar, que resulta ser el envío del formulario. Entonces, lo que aprendí hoy es que el navegador llama a onSubmit, como una acción predeterminada para presionar el botón Enviar. Incluso cuando presiona Intro, se activa un clic en el botón Enviar, lo que activa el envío de un formulario.

Pero ves cómo el orden de propagación del evento hace que esto sea realmente extraño.

  1. <input> [presionar tecla enter]
  2. <button type="submit"> [clic simulado]
  3. <Menu> [el evento se propaga fuera del portal]
  4. <Link> [la propagación llega al padre Link ]
  5. <Link> [llama e.preventDefault() ]
  6. => se cancela la respuesta predeterminada del navegador al hacer clic en el botón Enviar
  7. => formulario no enviado

Esto sucede a pesar de que ya pasamos el botón y el formulario en el DOM, y Link no tiene nada que ver con eso y tampoco tenía la intención de bloquear este comportamiento en absoluto.

La solución para mí (si alguien se encuentra con el mismo problema) fue la solución de uso común de envolver el contenido <Menu> en un div con onClick={e => e.stopPropagation()} . Pero mi punto es que perdí mucho tiempo rastreando el problema porque el comportamiento es realmente poco intuitivo.

La solución para mí (si alguien se encuentra con el mismo problema) fue la solución de uso común de envolver el contenido <Menu> en un div con onClick={e => e.stopPropagation()} . Pero mi punto es que perdí mucho tiempo rastreando el problema porque el comportamiento es realmente poco intuitivo.

Sí, cada _instancia individual del problema_ tiene la misma solución fácil, _una vez que haya experimentado el error y lo haya identificado correctamente_. Es un pozo de fallas de paredes muy empinadas que el equipo React ha labrado aquí, y es frustrante no escuchar ningún reconocimiento de ellos.

Pasé varios días tratando de depurar otro problema con mouseenter saliendo inesperadamente de los portales. Incluso con onMouseEnter={e => e.stopPropagation()} en el div del portal, los eventos aún se transmiten al Button propietario, como en https://github.com/facebook/react/issues/11387#issuecomment -340009465 (el primer comentar sobre este tema). mouseenter / mouseleave no se supone que burbujeen en primer lugar ...

Quizás aún más extraño, cuando veo una burbuja de evento sintético mouseenter de un portal a un botón, e.nativeEvent.type es mouseout . React está desencadenando un evento sintético no burbujeante basado en un evento nativo burbujeante, y a pesar de que se haya llamado stopPropagation en el evento sintético.

@gaearon @trueadm, este problema ha causado una frustración enorme y constante durante más de dos años. Este hilo es uno de los principales problemas activos en React. Por favor , ¿alguien del equipo podría contribuir aquí?

En mi caso, abrir el componente Ventana haciendo clic en un Botón hizo que la ventana desapareciera, ya que el clic en Ventana provocó un clic en el botón que provocó el cambio de estado.

Soy nuevo con React, principalmente uso un jQuery y vanillia JS, pero este es un error alucinante. Puede haber como el 1% de los casos en los que se esperaría este comportamiento ...

Me gustan las dos soluciones de @diegohaz , pero sigo pensando que createPortal debería tener una opción para detener la propagación del evento.

Mi caso de uso particular fue con los controladores onMouseLeave y onMouseEnter una descripción emergente activados por el descendiente del portal de su hijo, lo que no era deseado. Los eventos nativos solucionaron esto ignorando a los descendientes del portal, ya que no son descendientes de dom.

+1 para la opción de dejar de burbujear en los portales. Se sugirió colocar el portal como un hermano (en lugar de un niño) al componente donde se origina el oyente de eventos, pero creo que eso no funciona en muchos casos de uso (incluido el mío).

Finalmente parece que se eliminará ReactDOM.unstable_renderSubtreeIntoContainer , lo que significa que pronto no quedarán soluciones razonables para este problema ...

^ ayúdanos @trueadm -nobi eres nuestra única esperanza

Parece que hacer ping en GitHub no funciona 😞
¿Quizás alguien con una cuenta de Twitter activa podría tuitear sobre esto, etiquetando a uno de los colaboradores?

Agregando mi +1 para este problema. En Notion, actualmente utilizamos una implementación de portal personalizada que es anterior a React.createPortal , y reenviamos manualmente nuestros proveedores de contexto al nuevo árbol. Intenté adoptar React.createPortal pero el comportamiento de burbujeo inesperado me bloqueó:

La sugerencia de @sebmarkbage de mover el <Portal> fuera del componente <MenuItem> para convertirse en hermano solo resuelve el problema para un solo nivel de anidamiento. El problema persiste si tiene varios elementos de menú anidados (por ejemplo) que abren submenús.

Este problema se ha marcado automáticamente como obsoleto. Si este problema aún te afecta, deja un comentario (por ejemplo, "bump") y lo mantendremos abierto. Lamentamos no haber podido priorizarlo todavía. Si tiene alguna información adicional nueva, ¡inclúyala con su comentario!

Protuberancia.

Dan dejó un comentario sobre un tema relacionado:

@mogelbrod Actualmente no tengo nada que agregar a eso, pero algo como esto ( # 11387 (comentario) ) me parece razonable si está migrando un componente existente.

Seguimiento de Dan en el mismo número :

Gracias por el contexto sobre la solución. Dado que ya tiene ese conocimiento del dominio, el mejor siguiente paso es probablemente escribir un RFC para el comportamiento que desea y las alternativas que consideró: https://github.com/reactjs/rfcs. Tenga en cuenta que es poco probable que un RFC que diga "cambiemos esto" sea útil. Escribir un buen RFC requiere tanto la comprensión de por qué tenemos el comportamiento actual, _y_ un plan para cambiarlo de una manera que se adapte a sus casos de uso sin retroceder en otros.

Independientemente de eso , unstable_renderSubtreeIntoContainer no es compatible, así que desenredemos estas dos discusiones. No le agregaremos propagación de contexto porque toda la API está congelada y no se actualizará.

Definitivamente deberíamos publicar un React RFC para sugerir la adición de la bandera discutida, o quizás otra solución. ¿Alguien se siente particularmente interesado en redactar uno (quizás @justjake , @craigkovatch o @jquense)? ¡Si no, veré qué se me ocurre!

Si bien estoy interesado en desarrollar esta API, no tengo interés en redactar un RFC. Principalmente porque es un montón de trabajo y casi no hay posibilidad de que sea aceptado, no creo que el equipo central realmente considere los RFC que aún no están en su hoja de ruta.

@jquense No creo que esto sea exacto. Sí, es poco probable que fusionemos un RFC que no se alinee con la visión, ya que agregar una nueva API siempre es transversal e influye en todas las demás funciones planificadas. Y es justo que no comentemos a menudo sobre los que no funcionan. Sin embargo, los leemos, y especialmente cuando abordamos un tema en el que el ecosistema tiene más experiencia. Como ejemplos, https://github.com/reactjs/rfcs/pull/38 , https://github.com/ reactjs / rfcs / pull / 150 , https://github.com/reactjs/rfcs/pull/118 , https://github.com/reactjs/rfcs/pull/109 , https://github.com/reactjs/ rfcs / pull / 32 han influido en nuestro pensamiento a pesar de que no los hemos comentado explícitamente.

En otras palabras, nos acercamos a las RFC en parte como un mecanismo de investigación comunitaria. Este comentario de @mogelbrod (https://github.com/facebook/react/issues/16721#issuecomment-674748100) sobre por qué la solución es molesta es exactamente el tipo de cosas que nos gustaría ver en un RFC. Una consideración de las soluciones existentes y sus inconvenientes puede ser más valiosa que una propuesta de sugerencia de API concreta.

@gaearon Mi comentario no es para sugerir que el equipo no escuche los comentarios externos. Harás un buen trabajo con eso. Aunque creo que mi comentario es correcto. El _proceso_ tal como se desarrolla en el repositorio de RFC no da como resultado RFC aceptado de otras personas. Al observar qué RFC se fusionan, se trata de miembros del equipo o empleados de Facebook y nadie más. Las características que lo hacen suelen ser un poco diferentes y no participan en absoluto en el proceso de RFC (por ejemplo, ID isomorfos).

Estoy muy contento de escuchar que verás los otros RFC y que contribuyen al diseño de las funciones, pero "Fuimos influenciados por estos RFC externos a pesar de que nunca comentamos sobre ellos", creo que ilustra mi punto, no desafiarlo.

En otras palabras, nos acercamos a las RFC en parte como un mecanismo de investigación comunitaria.

Eso es muy razonable, pero no es lo que dice el repositorio de RFC _su_ enfoque, y no cómo otras personas generalmente piensan de los RFC. El proceso de RFC suele ser un enlace y un punto de comunicación entre el equipo y la comunidad, así como una especie de campo de juego parejo en términos de consideración y proceso de características.

Dejando a un lado los puntos más importantes sobre la gobernanza comunitaria. Pedirle a la gente que dediquen tiempo a redactar propuestas detalladas y luego las defiendan ante otros participantes externos mientras se encuentran en silencio por parte del equipo de reacción es desalentador y fortalece activamente la impresión de que FB solo se preocupa por sus propias necesidades de OSS. Lo cual apesta porque sé que no te sentirás ni diseñarás de esa manera.

Si el proceso de RFC se supone que es: "aquí es donde puede exponer sus inquietudes y casos de uso y los leeremos, cuando / si llegamos a un punto de poder implementar esta característica". Honestamente, ese es un buen enfoque. Creo que la comunidad se beneficiaría de eso que se explica explícitamente; de ​​lo contrario, las personas asumirán (y asumen) el mismo nivel de participación y participación que a menudo tienen otros procesos de RFC y luego se desanimarán activamente cuando eso no funcione. Ciertamente tengo esa impresión incluso con un poco más de conocimiento que otros colaboradores.

Claro, creo que estoy de acuerdo con todo eso. No quiero convertir esto en un meta-hilo, solo digo que, dado que la gente sigue haciendo ping sobre este hilo, lo más procesable que se puede hacer para avanzar es escribir una propuesta sobre cómo debería funcionar que tenga en cuenta las preocupaciones de ambos. lados en cuenta y compila una comprensión profunda del espacio del problema . Entiendo totalmente si esto no es algo en lo que a la gente le gustaría hundir el diente (en parte en función de cómo respondemos a las RFC), razón por la cual no lo he sugerido antes, pero como sigo recibiendo pings personales, quería sugerir que como una opción para alguien que está motivado.

bastante justo, este no es el lugar adecuado para obtener meta en RFC :)

@gaearon, este es el sexto problema más votado actualmente abierto en React, y el cuarto más comentado. Ha estado abierto desde que se lanzó React 16, y están a solo 2 meses de tener 3 años ahora. Sin embargo, ha habido muy poca participación del equipo central de React. Se siente muy despectivo decir que "depende de la comunidad proponer una solución" después de tanto tiempo y tanto dolor ha pasado y se ha producido. Tenga en cuenta que, aunque tiene algunas aplicaciones muy útiles, este comportamiento, que es el predeterminado, fue un error de diseño. No debería depender de la comunidad de RFC arreglarlo.

Lamento haber comentado sobre este tema y me retracto de mi sugerencia sobre el RFC de la comunidad. Tienes razón, probablemente sea una mala idea. Debo agregar que este tema se ha vuelto muy cargado de emociones y, como ser humano, personalmente encuentro difícil involucrarme con él, aunque entiendo que es importante y mucha gente lo siente fuertemente.

Permítanme responder brevemente sobre el estado de este hilo.

Primero, quiero disculparme con las personas que comentaron y se han sentido frustradas porque no continuamos con los seguimientos en este hilo. Si estuviera leyendo este problema desde afuera, mi impresión probablemente habría sido que el equipo de React ha cometido un error, no está dispuesto a admitirlo y está dispuesto a sentarse en una solución simple ("solo agregue un booleano, qué tan difícil ¡sea! ") durante más de dos años porque no se preocupan por la comunidad. Puedo entender totalmente cómo puede llegar a esta conclusión.

Sé que este problema está muy bien votado. Esto se ha mencionado varias veces en este hilo, tal vez desde la perspectiva de que si el equipo de React hubiera sabido que este es un gran problema, lo habríamos abordado antes. Sabemos que esto es un punto delicado: la gente nos envía regularmente mensajes privados al respecto o lo presenta como un ejemplo de cómo el equipo de React no se preocupa por la comunidad. Si bien reconozco plenamente que el silencio ha sido frustrante, la creciente presión de "simplemente hacer algo" ha hecho que sea más difícil abordar este tema de manera productiva.

Este problema tiene soluciones, lo que lo hace diferente a una vulnerabilidad de seguridad o un bloqueo que debe tratarse con urgencia. Sabemos que las soluciones de trabajo funcionan (pero no son ideales y pueden ser molestas) porque usamos algunas de ellas nosotros mismos, especialmente en el código que se escribió antes de React 16. Creo que podemos estar de acuerdo en que, si bien este problema ha sido sin duda frustrante para una gran número de personas, todavía se encuentra en una clase de problema diferente a un accidente o un problema de seguridad que debe resolverse dentro de un período de tiempo concreto.

Además, no estoy de acuerdo con el encuadre de que hay una solución simple que podemos implementar mañana. Incluso si consideramos el comportamiento inicial como un error (con lo que no estoy seguro de estar de acuerdo), significa que la barra para que el siguiente comportamiento maneje la variedad completa de casos de uso es aún mayor . Si arreglamos algunos casos pero dañamos otros, no habremos progresado y hemos creado un montón de abandono. Tenga en cuenta que no escucharemos sobre los casos en los que el comportamiento actual funciona bien en este tema. Solo lo escucharemos después de que lo rompamos.

Para darle un ejemplo, el comportamiento actual es realmente útil para el caso de uso de administración de enfoque declarativo que hemos estado investigando durante bastante tiempo. Es útil tratar el enfoque / desenfoque como si sucediera "dentro" de un modal con respecto al árbol de piezas, a pesar de ser un Portal. Si tuviéramos que enviar la propuesta "simple" createPortal(tree, boolean) sugerida en este hilo, este caso de uso no funcionaría porque el portal en sí no puede "saber" qué comportamiento queremos. Cualquier exploración de una posible solución debe considerar docenas de casos de uso y algunos de ellos aún no se comprenden completamente. Es necesario hacer esto en algún momento con seguridad, pero también es un gran compromiso de tiempo para hacerlo bien, y hasta ahora no hemos podido concentrarnos en ello.

Los eventos en particular son un área espinosa, por ejemplo, acabamos de hacer un montón de cambios que abordan problemas de años, y este ha sido un gran enfoque este año. Pero solo podemos hacer tantas cosas a la vez.

Generalmente, como equipo, tratamos de enfocarnos en pocos problemas en profundidad, en lugar de en muchos problemas de manera superficial. Desafortunadamente, esto significa que es posible que algunos defectos y lagunas conceptuales no se llenen durante años porque estamos en medio de corregir otras lagunas importantes o no tenemos un diseño alternativo elaborado que haya resuelto el problema para siempre. Sé que es frustrante escuchar esto y es parte de la razón por la que me mantuve alejado de este hilo. Algunos otros hilos similares se han convertido en explicaciones más profundas de los problemas y las posibles soluciones, que son útiles, pero este se ha convertido principalmente en una avalancha de "+1" y sugerencias para una solución "simple", por lo que ha sido difícil para comprometerse con él de manera significativa.

Sé que esta no es la respuesta que la gente quería escuchar, pero espero que sea mejor que ninguna respuesta.

Otra cosa que vale la pena mencionar es que algunos de los puntos débiles descritos en este hilo podrían haberse resuelto por otros medios. Por ejemplo:

Específicamente: llamar a stopPropagation en el evento de enfoque sintético para evitar que salga del portal hace que también se llame a stopPropagation en el evento de enfoque nativo en el controlador capturado de React en #document, lo que significa que no llegó a otro controlador capturado en

React ya no usa la fase de captura para emular el burbujeo y tampoco escucha más eventos en el documento. Entonces, sin descartar la frustración, definitivamente será necesario reevaluar todo lo que se ha publicado hasta ahora a la luz de los otros cambios.

Los eventos nativos aún burbujean, y dado que nuestro código React está alojado dentro de una aplicación mayoritariamente jQuery, el controlador global jQuery keyDown en

todavía recibe el evento.

De manera similar, React 17 adjuntará eventos a las raíces y los contenedores del portal (y de hecho detendrá la propagación nativa en ese punto), por lo que también esperaría que se resuelva.

Con respecto a los puntos sobre la eliminación de renderSubtreeIntoContainer . Literalmente, su única diferencia con ReactDOM.render es que propaga el contexto heredado. Dado que cualquier lanzamiento que no incluya renderSubtreeIntoContainer tampoco incluiría Legacy Context, ReactDOM.render seguiría siendo una alternativa 100% idéntica. Esto, por supuesto, no resuelve el problema más amplio, pero creo que la preocupación con renderSubtree específicamente está algo fuera de lugar.

@gaearon

Con respecto a los puntos sobre la eliminación de renderSubtreeIntoContainer . Literalmente, su única diferencia con ReactDOM.render es que propaga el contexto heredado. Dado que cualquier lanzamiento que no incluya renderSubtreeIntoContainer tampoco incluiría Legacy Context, ReactDOM.render seguiría siendo una alternativa 100% idéntica. Esto, por supuesto, no resuelve el problema más amplio, pero creo que la preocupación con renderSubtree específicamente está algo fuera de lugar.

Ahora que lo mencionó, me pregunto si el código a continuación sería una implementación válida y segura para un Portal React sin la propagación de eventos:

function Portal({ children }) {
  const containerRef = React.useRef();

  React.useEffect(() => {
    const container = document.createElement("div");
    containerRef.current = container;
    document.body.appendChild(container);
    return () => {
      ReactDOM.unmountComponentAtNode(container);
      document.body.removeChild(container);
    };
  }, []);

  React.useEffect(() => {
    ReactDOM.render(children, containerRef.current);
  }, [children]);

  return null;
}

CodeSandbox con algunas pruebas: https://codesandbox.io/s/react-portal-with-reactdom-render-m22dj?file=/src/App.js

Todavía hay un problema con no pasar el Contexto moderno, pero este no es un problema nuevo ( renderSubtree también se ve afectado). La solución alternativa es rodear su árbol con un montón de proveedores de contexto. En general, no es ideal anidar árboles, por lo que no recomendaría cambiar a este patrón en cualquier otro lugar que no sean escenarios de código existente heredado.

Una vez más, ¡muchas gracias por el artículo @gaearon!

Parece que agregar la lista de casos rotos + soluciones alternativas (actualizado para React v17) sería lo más productivo para alguien fuera del equipo central (¡corrígeme si me equivoco!).

Estoy abrumado en las próximas semanas, pero mi objetivo es hacerlo lo antes posible. Si alguien más puede hacer esto antes, o intervenir con fragmentos (como @diegohaz ), ¡sería increíble!

Agregar una lista de casos definitivamente sería útil, aunque yo diría que debe incluir no solo los casos rotos, sino también los casos en los que el comportamiento actual tiene sentido.

Si hay un espacio público para agregar, me complacerá agregar casos de uso de nuestras aplicaciones y como autor de la biblioteca de IU. En general, estoy de acuerdo con Dan en que, aunque a veces es molesto, es fácil evitarlo. Para los casos en los que desea el burbujeo de React, es muy difícil cubrir el caso sin la ayuda de React.

Agregar una lista de casos definitivamente sería útil, aunque yo diría que debe incluir no solo los casos rotos, sino también los casos en los que el comportamiento actual tiene sentido.

¡Me complacería incluirlos si alguien me puede señalar algún código fuente abierto / código extraído que se base en él! Como mencionaste anteriormente, es un desafío encontrarlo, ya que solo las personas que tienen problemas con el comportamiento actual están involucradas en este tema 😅

Si hay un espacio público para agregar, me complacerá agregar casos de uso de nuestras aplicaciones y como autor de la biblioteca de IU. En general, estoy de acuerdo con Dan en que, aunque a veces es molesto, es fácil evitarlo. Para los casos en los que desea el burbujeo de React, es muy difícil cubrir el caso sin la ayuda de React.

¿Algún espacio específico que tenga en mente, o compartir un código y una

Comencé un hilo aquí: https://github.com/facebook/react/issues/19637. Mantengámonos enfocados en ejemplos prácticos, mientras que este permanece para una discusión general.

¿Fue útil esta página
0 / 5 - 0 calificaciones