Creación de un componente de reacción de enfoque externo y controlador de clics

 

 

 

  • Design System Planning and Process, with Nathan Curtis
  • Smart Interface Design Patterns, 10h video + UX training

  • Índice
    1. La forma DOM de detectar clics externos
    2. Envolviendo la lógica de detección basada en jerarquía DOM en un componente de React
    3. El problema con la lógica de detección de clics externa basada en la jerarquía DOM
    4. Uso de la propiedad de instancia de clase y la delegación de eventos para detectar clics externos
    5. Outside Focus Detection
    6. Conclusion
      1. Related Resources

    En este artículo, veremos cómo crear un foco externo y un controlador de clics con React. react-focoAl hacerlo, aprenderá cómo recrear un componente React de código abierto ( ) desde cero. Para aprovechar al máximo este artículo, necesitará un conocimiento básico de las clases de JavaScript, la delegación de eventos DOM y React. Al final del artículo, sabrá cómo puede utilizar las propiedades de instancia de clase de JavaScript y la delegación de eventos para crear un componente de React que le ayude a detectar un clic o un enfoque fuera de cualquier componente de React.

     

    A menudo necesitamos detectar cuando se ha producido un clic fuera de un elemento o cuando el foco se ha desplazado fuera de él. Algunos de los ejemplos evidentes de este caso de uso son los menús desplegables, desplegables, información sobre herramientas y ventanas emergentes. Comencemos el proceso de creación de esta funcionalidad de detección.

    La forma DOM de detectar clics externos

    Si te pidieran que escribieras código para detectar si se produjo un clic dentro o fuera de un nodo DOM , ¿qué harías? Lo más probable es que utilices la Node.containsAPI DOM. Así lo explica MDN:

    El Node.contains()método devuelve un Booleanvalor que indica si un nodo es descendiente de un nodo determinado, es decir, el nodo en sí, uno de sus hijos directos ( childNodes), uno de los hijos directos de los hijos, etc.

    Probémoslo rápidamente. Hagamos un elemento para el que queremos detectar clic externo. Convenientemente le he dado una click-textclase.

    section div click inside and outside me /div/section
    const concernedElement = document.querySelector(".click-text");document.addEventListener("mousedown", (event) = { if (concernedElement.contains(event.target)) { console.log("Clicked Inside"); } else { console.log("Clicked Outside / Elsewhere"); }});

    Hicimos las siguientes cosas:

     

    1. Seleccionó el elemento HTML con la clase click-text.
    2. Coloque un detector de eventos con el mouse presionado documenty configure una función de devolución de llamada del controlador de eventos.
    3. En la función de devolución de llamada, estamos verificando si nuestro elemento en cuestión, para el cual tenemos que detectar un clic externo, contiene el elemento (incluido él mismo) que desencadenó el mousedownevento ( event.target).

    Si el elemento que desencadenó el evento de presionar el mouse es nuestro elemento en cuestión o cualquier elemento que esté dentro del elemento en cuestión, significa que hemos hecho clic dentro de nuestro elemento en cuestión.

    Hagamos clic dentro y fuera del elemento en Codesandbox a continuación y verifiquemos la consola.

    Envolviendo la lógica de detección basada en jerarquía DOM en un componente de React

    ¡Excelente! Hasta ahora vimos cómo usar Node.containsla API de DOM para detectar clics fuera de un elemento. Podemos envolver esa lógica en un componente de React. Podríamos nombrar nuestro nuevo componente React OutsideClickHandler. Nuestro OutsideClickHandlercomponente funcionará así:

    OutsideClickHandler onOutsideClick={() = { console.log("I am called whenever click happens outside of 'AnyOtherReactComponent' component") }} AnyOtherReactComponent //OutsideClickHandler

    OutsideClickHandlertoma dos accesorios:

    1. children
      Podría ser cualquier hijo React válido. En el ejemplo anterior, pasamos AnyOtherReactComponentel componente como OutsideClickHandlerhijo de.

    2. onOutsideClick
      Esta función se llamará si se produce un clic en cualquier lugar fuera del AnyOtherReactComponentcomponente.

    ¿Suena bien hasta ahora? De hecho, comencemos a construir nuestro OutsideClickHandlercomponente.

    import React from 'react';class OutsideClickHandler extends React.Component { render() { return this.props.children; }}

    Solo un componente básico de React. Hasta ahora no estamos haciendo mucho con ello. Simplemente devolvemos a los niños a medida que pasan a nuestro OutsideClickHandlercomponente. Envuelvamos el childrencon un elemento div y adjuntémosle una referencia de React.

    import React, { createRef } from 'react';class OutsideClickHandler extends React.Component { wrapperRef = createRef(); render() { return ( div ref={this.wrapperRef} {this.props.children} /div ) } }

    Usaremos esto refpara obtener acceso al objeto del nodo DOM asociado con el divelemento. Usando eso, recrearemos la lógica de detección externa que hicimos anteriormente.

    Adjuntemos mousedownel evento en el documento dentro componentDidMountdel método del ciclo de vida de React y limpiemos ese evento dentro del componentWillUnmountmétodo del ciclo de vida de React.

     

    class OutsideClickHandler extends React.Component { componentDidMount() { document .addEventListener('mousedown', this.handleClickOutside); } componentWillUnmount(){ document .removeEventListener('mousedown', this.handleClickOutside); } handleClickOutside = (event) = { // Here, we'll write the same outside click // detection logic as we used before. }}

    Ahora, escribamos el código de detección dentro de handleClickOutsidela función del controlador.

    class OutsideClickHandler extends React.Component { componentDidMount() { document .addEventListener('mousedown', this.handleClickOutside); } componentWillUnmount(){ document .removeEventListener('mousedown', this.handleClickOutside); } handleClickOutside = (event) = { if ( this.wrapperRef.current !this.wrapperRef.current.contains(event.target) ) { this.props.onOutsideClick(); } }}

    La lógica interna handleClickOutsidedel método dice lo siguiente:

    Si el nodo DOM en el que se hizo clic ( event.target) no era nuestro div contenedor ( this.wrapperRef.current) ni ningún nodo dentro de él ( !this.wrapperRef.current.contains(event.target)), llamamos a la onOutsideClickpropiedad.

    Esto debería funcionar de la misma manera que funcionaba antes la detección de clics externos. Intentemos hacer clic fuera del elemento de texto gris en el cuadro de códigos a continuación y observemos la consola:

    El problema con la lógica de detección de clics externa basada en la jerarquía DOM

    Pero hay un problema. Nuestro componente React no funciona si alguno de sus elementos secundarios se representa en un portal de React.

    ¿Pero qué son los portales React?

    "Los portales proporcionan una forma de primera clase de representar elementos secundarios en un nodo DOM que existe fuera de la jerarquía DOM del componente principal".

    — Reaccionar documentos para portales

    Los niños de React representados en el portal de React no siguen la jerarquía DOM de arriba hacia abajo. ( Vista previa grande )

    En la imagen de arriba, puede ver que, aunque Tooltipel componente React es hijo del Containercomponente React, si inspeccionamos el DOM encontramos que el nodo DOM Tooltip en realidad reside en una estructura DOM completamente separada, es decir, no está dentro del nodo DOM del contenedor.

    El problema es que en nuestra lógica de detección externa hasta ahora, asumimos que los hijos de OutsideClickHandlerserán sus descendientes directos en el árbol DOM. Este no es el caso de los portales React. Si los hijos de nuestro componente se procesan en un portal React, es decir, se procesan en un nodo DOM separado que está fuera de la jerarquía container diven la que nuestro OutsideClickHandlercomponente representa a sus hijos, entonces la Node.containslógica falla.

     

    ¿Cómo podría fallar? Si intenta hacer clic en los elementos secundarios de nuestro OutsideClickHandlercomponente, que se representa en un nodo DOM separado mediante portales React, nuestro componente registrará un clic externo, lo cual no debería. Ver por ti mismo:

    Pruébalo:

    Aunque la ventana emergente que se abre al hacer clic en el botón es secundaria del OutsideClickHandlercomponente, no detecta que no está fuera de él y la cierra cuando se hace clic en él.

    Uso de la propiedad de instancia de clase y la delegación de eventos para detectar clics externos

    Entonces ¿cuál podría ser la solución? Seguramente no podemos confiar en que DOM nos diga si el clic se produce fuera de cualquier lugar. Tendremos que hacer algo con JavaScript reescribiendo OutsideClickHandlerla implementación.

    Empecemos con una pizarra en blanco. Entonces en este momento OutsideClickHandlerhay una clase React vacía. Calefactor electrico

    El quid de la detección correcta del clic externo es:

    1. No confiar en la estructura DOM.
    2. Para almacenar el estado de "clic" en algún lugar del código JavaScript.

    Para este evento una delegación vendrá en nuestra ayuda. Tomemos un ejemplo del mismo botón y ventana emergente que vimos arriba en el GIF de arriba.

    Tenemos dos hijos de nuestra OutsideClickHandlerfunción. Un botón y una ventana emergente, que se representa en un portal fuera de la jerarquía DOM de OutsideClickHandler, al hacer clic en el botón, así:

    Jerarquía DOM de document, el OutsideClickHandlercomponente y sus hijos representados en el portal React. ( Vista previa grande )

    Cuando se hace clic en cualquiera de nuestros hijos, configuramos una variable clickCaptureden true. Si se hace clic en algo fuera de ellos, el valor de clickCapturedpermanecerá false.

    Almacenaremos clickCapturedel valor de en:

    1. Una propiedad de instancia de clase, si está utilizando un componente de reacción de clase.
    2. Una referencia, si está utilizando un componente funcional de React.

    No utilizamos el estado de React para almacenar clickCapturedel valor porque no representamos nada basado en estos clickCaptureddatos. La finalidad de clickCapturedes efímera y finaliza en cuanto detectamos si el clic se ha producido en el interior o en el exterior.

    Veamos en la imagen debajo la lógica para configurar clickCaptured:

    Cuando se hace clic en cualquiera de los elementos secundarios del OutsideClickHandlercomponente, lo configuramos clickCaptureden true. ( Vista previa grande )

    Whenever a click happens anywhere, it bubbles up in React by default. It’ll reach to the document eventually.

    The value of the clickCaptured variable when mousedown event bubbles upto document — for both inside and outside click cases. (Large preview)

     

    When the click reaches document, there are two things that might have happened:

    1. clickCaptured will be true, if children where clicked.
    2. clickCaptured will be false, if anywhere outside of them was clicked.

    In the document’s event listener we will do two things now:

    1. If clickCaptured is true, we fire an outside click handler that the user of OutsideClickHandler might have given us through a prop.
    2. We reset clickCaptured to false, so that we are ready for another click detection.

    Detecting if click happened inside or outside of React component by checking clickCapture’s value when mousedown event reaches document. (Large preview)

    Let’s translate this into code.

    import React from 'react'class OutsideClickHandler extends React.Component { clickCaptured = false; render() { if ( typeof this.props.children === 'function' ) { return this.props.children(this.getProps()) } return this.renderComponent() }}

    We have the following things:

    1. set initial value of clickCaptured instance property to false.
    2. In the render method, we check if children prop is a function. If it is, we call it and pass it all the props we want to give it by calling getProps class method. We haven’t implemented getProps just yet.
    3. If the children prop is not a function, we call renderComponent method. Let’s implement this method now.
    class OutsideClickHandler extends React.Component { renderComponent() { return React.createElement( this.props.component || 'span', this.getProps(), this.props.children ) }}

    Since we aren’t using JSX, we are directly using React’s createElement API to wrap our children in either this.props.component or a span. this.props.component can be a React component or any of the HTML element’s tag name like ‘div’, ‘section’, etc. We pass all the props that we want to pass to our newly created element by calling getProps class method as the second argument.

    Let’s write the getProps method now:

    class OutsideClickHandler extends React.Component { getProps() { return { onMouseDown: this.innerClick, onTouchStart: this.innerClick }; }}

    Our newly created React element, will have the following props passed down to it: onMouseDown and onTouchStart for touch devices. Both of their values is the innerClick class method.

    class OutsideClickHandler extends React.Component { innerClick = () = { this.clickCaptured = true; }}

    If our new React component or anything inside of it — which could be a React portal — is clicked, we set the clickCaptured class instance property to true. Now, let’s add the mousedown and touchstart events to the document, so that we can capture the event that is bubbling up from below.

     

    class OutsideClickHandler extends React.Component { componentDidMount(){ document.addEventListener('mousedown', this.documentClick); document.addEventListener('touchstart', this.documentClick); } componentWillUnmount(){ document.removeEventListener('mousedown', this.documentClick); document.removeEventListener('touchstart', this.documentClick); } documentClick = (event) = { if (!this.clickCaptured this.props.onClickOutside) { this.props.onClickOutside(event); } this.clickCaptured = false; };}

    In the document mousedown and touchstart event handlers, we are checking if clickCaptured is falsy.

    1. clickCaptured would only be true if children of our React component would have been clicked.
    2. If anything else would have been clicked clickCaptured would be false, and we’d know that outside click has happened.

    If clickCaptured is falsy, we’ll call the onClickOutside method passed down in a prop to our OutsideClickHandler component.

    That’s it! Let’s confirm that if we click inside the popover it doesn’t get closed now, as it was before:

    Let’s try it out:

    Wonderful!

    Outside Focus Detection

    Now let’s take a step further. Let’s also add functionality to detect when focus has shifted outside of a React component. It’s going to be very similar implementation as we’ve done with click detection. Let’s write the code.

    class OutsideClickHandler extends React.Component { focusCaptured = false innerFocus = () = { this.focusCaptured = true; }componentDidMount(){ document.addEventListener('mousedown', this.documentClick); document.addEventListener('touchstart', this.documentClick); document.addEventListener('focusin', this.documentFocus); }componentWillUnmount(){ document.removeEventListener('mousedown', this.documentClick); document.removeEventListener('touchstart', this.documentClick); document.removeEventListener('focusin', this.documentFocus); }documentFocus = (event) = { if (!this.focusCaptured this.props.onFocusOutside) { this.props.onFocusOutside(event); } this.focusCaptured = false; };getProps() { return { onMouseDown: this.innerClick, onTouchStart: this.innerClick, onFocus: this.innerFocus }; }

    Everything’s added mostly in the same fashion, except for one thing. You might have noticed that though we are adding an onFocus react event handler on our children, we are setting a focusin event listener to our document. Why not a focus event you say? Because, , Starting from v17, React now maps onFocus React event to focusin native event internally.

    In case you are using v16 or before, instead of adding a focusin event handler to the document, you’ll have to add a focus event in capture phase instead. So that’ll be:

    document.addEventListener('focus', this.documentFocus, true);

    Why in capture phase you might ask? Because as weird as it is, focus event doesn’t bubble up.

    Since I’m using v17 in all my examples, I’m going to go ahead use the former. Let’s see what we have here:

    Let’s try it out ourselves, try clicking inside and outside of the pink background. Also use Tab and Shift + Tab keys ( in Chrome, Firefox, Edge ) or Opt/Alt + Tab and Opt/Alt + Shift + Tab (in Safari ) to toggle focussing between inner and outer button and see how focus status changes.

    Conclusion

    In this article, we learned that the most straightforward way to detect a click outside of a DOM node in JavaScript is by using Node.contains DOM API. I explained the importance of knowing why using the same method to detect clicks outside of a React component doesn’t work when the React component has children which render in a React portal.

    Also, now you know how to use a class instance property alongside an event delegation to correctly detect whether a click happened outside of a React component, as well as how to extend the same detection technique to outside focus detection of a React component with the focusin event caveat.

    Related Resources

    1. React Foco Github Repository
    2. mdn documentation for Node.contains DOM api
    3. React docs for portals
    4. React createElement API
    5. React Github codebase Pull Request for mapping onFocus and onBlur methods to internally use focusin and focusout native events.
    6. Delegating Focus and Blur events

    (ks, vf, yk, il)Explore more on

    • Tools
    • React
    • JavaScript





    Tal vez te puede interesar:

    1. Creación de su propia biblioteca de validación de React: las características (Parte 2)
    2. Introducción a Quasar Framework: creación de aplicaciones multiplataforma
    3. Creación de un componente web retro que se puede arrastrar con iluminación
    4. Creación y acoplamiento de una aplicación Node.js con arquitectura sin estado con la ayuda de Kinsta

    Creación de un componente de reacción de enfoque externo y controlador de clics

    Creación de un componente de reacción de enfoque externo y controlador de clics

    Design System Planning and Process, with Nathan Curtis Smart Interface Design Patterns, 10h video + UX training Índice

    programar

    es

    https://aprendeprogramando.es/static/images/programar-creacion-de-un-componente-de-reaccion-de-enfoque-externo-y-controlador-de-clics-1086-0.jpg

    2024-05-21

     

    Creación de un componente de reacción de enfoque externo y controlador de clics
    Creación de un componente de reacción de enfoque externo y controlador de clics

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20