loading...

Menu dropdown que fecha quando clicado fora do container (em react)

juliocarneiro profile image Júlio Carneiro ・5 min read

Fala pessoal, tranquilo? Preparei um passo a passo pra ensinar vocês como fazer um menu “dropdown” com o react, vou usar “bootstrap”, e ensinar também como fazemos para quando for clicado fora do menu ele fechar, para melhorar um pouco a experiência do usuário.

Mal a demora pra postar como sempre, mas esse ano as coisas vão mudar e eu to tendo umas idéias que o lugar onde trabalho está me possibilitando de por em prática, to muito ansioso e logo mais dou mais notícias sobre isso por aqui.

Então bora lá começar criando um projeto com o “create-react-app”, pra quem ainda não tem “npm i -g create-react-app”:

$ create-react-app dropdown

Agora bora procurar alguma “cdn” que tenha o “bootstrap” pra gente baixar ou linkar pra poder testar, eu escolhi esta aqui, que é um tema do “bootstrap” que eu gosto bastante te usar:

https://bootswatch.com/4/lux/bootstrap.min.css

Se quiser mais detalhes sobre este tema, tem alguns exemplos de elementos neste link.

Construindo o menu

Vamos começar abrindo na pasta “/public” o arquivo “index.html” e vamos adicionar o link para o “cdn” do tema “bootstrap”:

<!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <link rel="stylesheet" href="https://bootswatch.com/4/lux/bootstrap.min.css">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <meta name="theme-color" content="#000000" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

Peguei um html de exemplo para ilustrarmos o efeito, então vamos fazer um menu “dropdown” padrão do “bootstrap”, mas como ele funciona? Abaixo vai um gif do dev tools para entendermos o que acontece quando clicamos no “dropdown” pro menu abrir:

Passando pro react

Conseguimos ver que ao clicar, mudamos algumas classes para podermos mostrar o menu, isso é meio padrão em qualquer lib css. Vamos passar essa mudança pro react fazendo a lógica baseada em estados, pra começar eu vou mostrar pra vocês o corpo do menu que eu peguei de exemplo e passei para o react:

import React, { Component } from "react";

class App extends Component {
  render() {
    return (
      <div className="bs-component">
        <ul className="nav nav-tabs">
          <li className="nav-item">
            <a className="nav-link" data-toggle="tab" href="#home">
Home</a>
          </li>
          <li className="nav-item">
            <a className="nav-link show" data-toggle="tab" href="#profile">Profile</a>
          </li>
          <li className="nav-item">
            <a className="nav-link disabled" href="#">Disabled</a
          </li>
          <li className="nav-item dropdown">
            <a className="nav-link dropdown-toggle" href="#">Dropdown</a>
            <div className="dropdown-menu" x-placement="bottom-start">
              <a className="dropdown-item" href="#">Action</a>
              <a className="dropdown-item" href="#">Another action</a>
              <a className="dropdown-item" href="#">Something else here</a>
              <div className="dropdown-divider" />
              <a className="dropdown-item" href="#">Separated link</a>
            </div>
          </li>
        </ul>
      </div>
    );

  }

}

export default App;

Como vimos antes, a div com classe “dropdown-menu” vai receber uma outra classe “show” para mostrar o menu então vamos começar setando um novo estado para podermos manipular em nossa div e uma função que vamos utilizar para modificar o estado quando clicarmos no botão:

state = {
  open: false
};

handleButtonClick = () => {
  this.setState(state => {
    return {
      open: !state.open
    };
  });
};

handleClickOutside = event => {
  if (this.container.current && !this.container.current.contains(event.target)){
    this.setState({
      open: false
    });
  }
};

Usamos o método “setState” de retorno de chamada aqui porque estamos fazendo referência ao estado anterior. Fazendo deste jeito as atualizações de estado são feitas na ordem correta.

Depois de fazer a lógica do botão podemos fazer um “if” para a mudança de classe, amarrando ele no nosso estado “open”:

<div className={this.state.open ? "dropdown-menu show" : "dropdown-menu"}>

E no nosso link () com o nome de “dropdown” vamos acrescentar um “onClick” para poder abrir o menu quando clicarmos nele:

<a className="nav-link dropdown-toggle" onClick={this.handleButtonClick} href="#">Dropdown</a>

Lembrando que, como fizemos uma arrow function não precisamos fazer o “bind” no “onClick”, isso melhora bastante nossa performance na aplicação em geral.

Com este código acima já conseguimos ter o resultado que esperávamos, ao clicar no botão “dropdown” ele abre o menu, e se clicarmos de novo ele fecha, porém, temos um problema de usabilidade que merece atenção e podemos facilmente implementar uma solução onde temos uma experiência de usuário melhor.

O que e como fazer para melhorar isso? Quando clicamos no menu ele só fecha ao clicarmos no mesmo local, então o menu sempre vai ficar acima do conteúdo caso eu não feche pelo mesmo botão que abrimos. No caso podemos construir uma solução onde ao clicarmos fora do menu ele feche. Vamos criar uma referência na div pai do menu onde vamos escutar o click, caso tenha ocorrido o click fora do container o menu fecha:

container= React.createRef();
state = {
  open: false,
};

Agora passamos nosso ref para nosso elemento do DOM e, em seguida, teremos acesso a esta div.

<div className="bs-component" ref={this.container}>

Aqui é que a mágica acontece de fato, adicionamos listeners “mousedown” para podermos identificar a ação de fora do elemento e executar nossa função “handleClickOutside”, logo após limpamos os listeners pelo “componentWillUnmount”.

componentDidMount() {
    document.addEventListener("mousedown", this.handleClickOutside);
}
componentWillUnmount() {
  document.removeEventListener("mousedown", this.handleClickOutside);
}

Agora precisamos verificar para ter certeza de que o nosso “current” é realmente preenchido com um elemento DOM. Então, usando o método DOM “contains”, perguntamos ao nosso container se temos o “event.target” no elemento DOM que foi clicado.

handleClickOutside = event => {
  if (this.container.current && !this.container.current.contains(event.target)) {
    this.setState({
      open: false,
    });
  }
};

Se não tivermos o menu clicado, significa que ele está fora da nossa div container e precisamos fechar nosso menu. Então, chamamos o “setState” e definimos a prop open como “false”.

Nosso botão está dentro da nossa div container, então ele irá executar a função de ativação normal e fechar o menu quando for clicado.

Espero que tenham curtido o tutorial, o código se encontra no meu github, mas aconselho fortemente que você acompanhe e escreva os códigos ao invés de copiar e colar pois estará aprendendo melhor. Um forte abraço e até 2019 com uma penca de projeto novo.

https://github.com/juliocarneiro/dropdown-react

Posted on by:

Discussion

markdown guide