Controlando dicas e menus pop-up usando componentes compostos no React

Do?acan Bilgili Blocked Desbloquear Seguir Seguindo 7 de janeiro

Ocultar mais informações por trás de uma interação com um ícone, botão ou texto é uma boa maneira de deixar sua interface limpa e organizada. É aqui que as dicas de ferramentas e os menus pop-up entram em ação.

Este artigo apresentará a abordagem que segui para implementar um componente de controlador de dica de ferramenta no React. Embora eu chame de dica de ferramenta, pode ser qualquer coisa que você queira exibir quando um elemento dentro do DOM for interagido por meio de um clique ou passar o mouse.

Um exemplo de menu pop-up do Medium.

Eu só vou cobrir os fundamentos aqui. No entanto, se você estiver interessado em ver as funcionalidades detalhadas, confira o repositório do Github para o projeto completo.

dbilgili / React-Tooltip-Controller
Este é um componente React rico em recursos para controlar dicas de ferramentas / menus pop-up – dbilgili / React-Tooltip-Controller github.com

Vamos começar listando alguns requisitos básicos para o componente do controlador de dica de ferramenta.

  • Ele deve se integrar perfeitamente à marcação JSX existente

Ser capaz de integrar o componente ao seu JSX existente sem introduzir elementos HTML extras, como <div> ou <span> é importante no sentido de que ele não afeta a saída e o estilo JSX consequentemente.

  • Deve aparecer no topo de todos os outros elementos

É óbvio que a dica de ferramenta não deve aparecer sob quaisquer outros elementos dentro do DOM. Usar o z-index pode não ajudar em alguns casos, já que seu uso não é tão simples quanto parece. Portanto, a dica de ferramenta deve aparecer na parte inferior do <body> para garantir sua hierarquia no topo da árvore DOM.

  • Deve ser interativo quando necessário

Uma dica de ferramenta passiva mostrando apenas texto ou uma imagem geralmente não requer interação. Pode até ser esperado que feche quando clicado. Mas o exemplo mostrado acima, por exemplo, exige que a dica de ferramenta permaneça ativa quando clicada para usar os botões / links dentro dela.

Considere os seguintes blocos de código e suponha que queremos criar uma interação em um dos elementos <li> .

Embrulhar esse elemento <li> específico junto com o componente de dica de ferramenta, que queremos controlar, é a abordagem que seguiremos.

Isso nos dará a flexibilidade de ter controle total do elemento selecionado e do componente de dica de ferramenta ou marcação JSX que incluímos dentro do componente <Controller> .

Podemos adicionar ouvintes de eventos, estilos de controle, buscar informações de posição, etc. Quando você tem um ou vários componentes envolvidos por outro componente, esse padrão de design é geralmente chamado de Componentes compostos.

Componentes

Vamos criar dois componentes: <Controller> e <Select> .

<Controller> manterá a parte do código que queremos conversar um com o outro; tooltip e o elemento selecionado. <Select> componente <Select> , por outro lado, somente manipulará o elemento DOM selecionado, que controlará a dica de ferramenta.

APIs

Como o componente <Controller> envolverá dois filhos, usaremos as seguintes APIs de Reação para lidar com esses filhos.

React.Children

React.Children é uma das APIs de React usadas para manipular objetos filhos de um componente, o que significa que qualquer coisa envolvida por um componente pode ser acessada como um suporte dentro de um componente. O método de map chamadas em React.Children com this.props.children nos ajuda a iterar sobre isso e criar uma nova matriz de filhos modificados a partir dele.

React.cloneElement

Essa API cria um clone da entrada e retorna um novo elemento de reação. Combinar isso com React.Children nos dá a capacidade de manipular os componentes filhos do componente <Controller> que vamos implementar.

ReactDOM.createPortal

Como pretendemos montar o componente tooltip na parte inferior do body , precisamos evitar que o React o acrescente ao nó pai mais próximo por padrão. Portal é a solução nativa fornecida pelo React. Podemos especificar onde e qual elemento montar no DOM.

Comece com o básico

Antes de começarmos a implementar funcionalidades detalhadas, vamos dar uma rápida olhada na estrutura básica do componente <Controller> .

Observe o uso de React.Children com a função map para iterar sobre todos os filhos e retornar um clone de cada filho com React.cloneElement .

Além disso, o uso de React.createPortal é direto, ele pega o filho clonado e o converte em document.body , que retorna o elemento <body> no DOM.

Observe que, para distinguir entre filhos do <Controller> , usei a propriedade displayName , que será definida como uma propriedade estática no componente <Select> posteriormente.

Funcionalidades

O próximo passo é adicionar as seguintes funcionalidades.

  • Adicione um ouvinte de evento ao elemento selecionado para controlar a dica de ferramenta
  • Posicione a dica de ferramenta em relação ao elemento selecionado
  • Detectar clique fora do componente tooltip para fechá-lo
  • Evite que a dica de ferramenta borbole de eventos, para que ela não feche quando clicada nela

1. Abra a dica de ferramenta

Conectando o componente da dica de ferramenta a um elemento.

Comece com a criação do estado de <Controller>

isOpen é para montar e desmontar o componente tooltip / marcação JSX e o style é para posicionar a dica de ferramenta em relação ao elemento selecionado . A dica de ferramenta está absolutamente posicionada em relação ao corpo por padrão. Assim, obtendo as informações de posição e tamanho do elemento selecionado, podemos posicionar a dica de ferramenta em relação a ela.

Agora, crie as funções que controlam o estado da dica de ferramenta

Em seguida, isso está usando os estados isOpen e style para mostrar / ocultar e posicionar o componente tooltip, respectivamente. Além disso, é necessário passar a função open() para o componente <Select> como um prop para que, quando o elemento selecionado for clicado, possamos mostrar a dica de ferramenta.

O segundo argumento para React.cloneElement é o novo React.cloneElement que estamos passando para o componente <Select> .

Vamos dar uma olhada no componente <Select> e ver como lidamos com o filho clonado e os adereços.

Observe a definição estática de displayName que usamos anteriormente em <Controller> .

Embora pudéssemos simplesmente return children no método render do componente <Select> , o uso da API cloneElement nos dá a capacidade de criar um novo clone dos filhos prop com o manipulador de eventos onClick .

E nós atribuímos o prop open a este manipulador de evento onClick para chamar a função open() no componente <Controller> , que, como resultado, mostra a dica de ferramenta no canto superior esquerdo da tela.

Agora, é hora de obter as informações de posição e tamanho do elemento filho clonado dentro do componente <Select> e passar esses dados de volta para <Controller> para serem usados com o estado de style para posicionar a dica de ferramenta.

2. Posicione a dica de ferramenta

Posicionando a dica de ferramenta em relação a um elemento.

Obter a posição do elemento dentro do componente <Select> requer o uso do atributo ref . O ReactJS tem seu próprio jeito de criar refs. Depois de definir um ref usando React.createRef() e anexá-lo a um elemento, você pode consultá-lo em todo o componente.

Chamar o método getBoundingClientRect() no ref selectedElement retorna as informações de posição e tamanho do elemento. Passaremos essas informações do componente <Select> para o componente <Controller> , implantando uma função como prop em <Select> .

Uma vez que a função getPos() esteja disponível para o componente <Select> como um prop, chamá-la dentro do gancho do ciclo de vida componentDidMount atualiza a variável de estado do style de <Component> e posiciona a dica relativa à base esquerda do elemento selecionado.

3. Feche a dica de ferramenta

Alternando a dica de ferramenta.

Até agora, controlamos a dica de ferramenta por meio de um elemento selecionado e a posicionamos em relação a esse elemento. Agora, o próximo passo é implementar o mecanismo para fechar a dica de ferramenta quando clicado fora dela.

É bastante simples ouvir eventos de clique no objeto de window e alternar a variável de estado isOpen . No entanto, essa abordagem requer alguns pequenos truques para que ela funcione corretamente.

Considere o seguinte fragmento do componente <Controller> .

Quando o componente é atualizado, adicionamos ou removemos um ouvinte de evento para o objeto de window acordo com o estado da dica de ferramenta. No entanto, essa tentativa resulta em uma abertura e fechamento de uma ferramenta praticamente ao mesmo tempo.

Eu criei duas soluções diferentes para esse problema:

  1. Em vez de ouvir click evento tanto para open() e close() funções, ouvindo mousedown e mouseup para close() e open() funções, respectivamente impede a close() função que está sendo chamado, uma vez que ouve mousedown evento que aconteceu antes a dica de ferramenta foi aberta.

No entanto, essa abordagem falhará se você tentar fechar a dica de ferramenta clicando no elemento selecionado.

2. Esta segunda abordagem é um pouco mais avançada em termos do raciocínio por trás dela. Usando o método setTimeout com 0 milissegundos de atraso ou sem qualquer atraso de tempo definido filas uma nova tarefa a ser executada pelo próximo ciclo de eventos. Embora o uso de 0 milissegundos geralmente descreva uma tarefa que deve ser executada imediatamente, esse não é o caso da natureza síncrona de encadeamento único do JavaScript. Quando o setTimeout é usado, ele simplesmente cria um retorno de chamada assíncrono. Você pode consultar os documentos da Web específicos do MDN para obter uma explicação detalhada sobre o tópico.

O snippet abaixo garante que um ouvinte de evento será adicionado ou removido depois que as tarefas de interação com o elemento selecionado forem executadas.

Embora clicar no elemento selecionado chame a função open() , o ouvinte de evento no objeto de window chama a função close() depois e fecha a dica de ferramenta.

4. Evitar borbulhamento de eventos

Evitando o borbulhamento de eventos na dica de ferramenta.

Como mencionado anteriormente, em alguns casos específicos, talvez seja necessário impedir que a dica de ferramenta seja fechada quando clicada nela. O motivo para clicar na dica de ferramenta chama a função close() é o resultado do evento borbulhando .

Quando um evento, como onClick , acontece em um elemento, ele também é chamado no pai e em todos os outros ancestrais. No nosso caso, uma vez que a dica de ferramenta é um filho do body e do body tem um evento de clique anexado, clicar na dica de ferramenta chama a função anexada ao evento de clique no body eventualmente.

Para evitar esse fenômeno, precisamos especificar explicitamente no manipulador de cliques do elemento filho que os eventos não devem ir mais adiante até os ancestrais.

O método event.stopPropagation() é o que precisamos usar no manipulador de eventos onClick para interromper a propagação de eventos onClick no DOM.

Conclusão

Depois de ler este artigo, você deve se familiarizar com as APIs React mencionadas e ter uma ideia geral sobre como utilizá-las e combiná-las para estruturar componentes compostos para tarefas mais específicas. Ter diferentes componentes conversando entre si internamente pode tornar seu código mais estruturado e intencional.

Plug: LogRocket , um DVR para aplicativos da web

https://logrocket.com/signup/

LogRocket é uma ferramenta de registro de front-end que permite que você repita problemas como se eles tivessem ocorrido em seu próprio navegador. Em vez de adivinhar por que os erros ocorrem ou solicitar aos usuários capturas de tela e log dumps, o LogRocket permite que você repita a sessão para entender rapidamente o que deu errado. Ele funciona perfeitamente com qualquer aplicativo, independentemente do framework, e possui plugins para registrar o contexto adicional do Redux, Vuex e @ ngrx / store.

Além de registrar as ações e o estado do Redux, o LogRocket registra logs do console, erros de JavaScript, rastreamentos de pilha, solicitações / respostas de rede com cabeçalhos + corpos, metadados do navegador e logs personalizados. Ele também instrumenta o DOM para gravar o HTML e CSS na página, recriando vídeos com pixels perfeitos até mesmo dos aplicativos de página única mais complexos.

Experimente Grátis.