Respondendo a Eventos
O React permite você adicionar manipuladores de eventos (event handlers) ao seu JSX. Os manipuladores de eventos são funções independentes que são acionadas em resposta a interações como clicar com o mouse, passar o cursor do mouse sobre um certo elemento, focar em campos de formulário, entre outros.
Você aprenderá
- Diferentes maneiras de escrever um manipulador de eventos
- Como herdar a lógica de manipulação de eventos de um componente pai
- Como os eventos se propagam e como pará-los
Adicionando manipuladores de eventos
Para adicionar um manipulador de eventos, primeiro defina uma função e depois passe-a como uma prop para o elemento JSX desejado. Por exemplo, aqui temos um botão que ainda não faz nada:
export default function Button() { return ( <button> Este botão não faz nada </button> ); }
Seguindo esses três passos, você poderá fazer com que uma mensagem seja exibida quando um usuário clicar no botão:
- Declare uma função chamada
handleClick
dentro do seu componenteButton
. - Implemente a lógica dentro dessa função (utilize o método
alert
para exibir a mensagem). - Adicione
onClick={handleClick}
ao JSX do elemento<button>
.
export default function Button() { function handleClick() { alert('Você clicou no botão!'); } return ( <button onClick={handleClick}> Clique neste botão </button> ); }
Você definiu a função handleClick
e depois a passou como prop para o elmento <button>
. O handleClick
é um manipulador de eventos. As funções de manipulador de eventos geralmente:
- São definidas dentro de seus componentes.
- Tem nomes que começam com a palavra
handle
, seguida do nome do evento.
Por convenção, é comum nomear manipuladores de eventos com a palavra handle
seguida do nome do evento. Você verá frequentemente nomes como onClick={handleClick}
, onMouseEnter={handleMouseEnter}
, e assim por diante.
Se desejar, você pode definir um manipulador de eventos diretamente na prop da tag JSX:
<button onClick={function handleClick() {
alert('Você clicou no botão!');
}}>
Ou, de forma mais concisa, usando uma arrow function:
<button onClick={() => {
alert('Você clicou no botão!');
}}>
Todos esses estilos são equivalentes. Os manipuladores de eventos diretamente na prop são adequados para funções pequenas.
Lendo props em manipuladores de eventos
Como os manipuladores de eventos são declarados dentro de um componente, eles têm acesso às props do componente. No examplo abaixo, temos um botão que, ao ser clicado, exibe um alerta com a prop message
:
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="Reproduzindo!"> Reproduzir Filme </AlertButton> <AlertButton message="Enviando!"> Enviar Imagem </AlertButton> </div> ); }
Isso permite que esses dois botões exibam mensagens diferentes. Tente alterar as mensagens passadas para eles.
Passando manipuladores de eventos como props
É comum que o componente pai defina o manipulador de eventos de um componente filho. Por exemplo, considere os botões: dependendo do contexto em que o componente Button
é usado, pode ser necessário executar funções diferentes, talvez um reproduza um filme e outro faça o upload de uma imagem.
Para fazer isso, passe uma prop que o componente recebe de seu pai como o manipulador de eventos da seguinte forma:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`Reproduzindo ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Reproduzir "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('Enviando!')}> Enviar Imagem </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="O Serviço de Entregas da Kiki" /> <UploadButton /> </div> ); }
Aqui, o componente Toolbar
renderiza o componente PlayButton
e o componenteUploadButton
:
- O
PlayButton
passa ohandlePlayClick
como proponClick
para oButton
dentro dele. - O
UploadButton
passa() => alert('Enviando!')
como a proponClick
para oButton
dentro.
Por fim, seu componente Button
aceita uma prop chamada onClick
. Ele passa essa prop diretamente para o elemento <button>
nativo do navegador usando onClick={onClick}
. Isso diz ao React para chamar a função quando o botão for clicado.
Se você usa um design system, é comum que componentes, como botões, contenham estilo mas não especifiquem o comportamento. Em vez disso, componentes como PlayButton
e UploadButton
passarão os manipuladores de eventos para baixo.
Nomeando props de manipuladores de eventos
Os componentes nativos, como <button>
e <div>
, suportam apenas os nomes de eventos do navegador, tais como onClick
. No entanto, quando você está criando seus próprios componentes, você pode nomear os manipuladores de eventos da forma que preferir.
Por convenção, as props dos manipuladores de eventos devem começar com o termo on
, seguido por uma letra maiúscula.
Por exemplo, a prop onClick
do componente Button
poderia ter sido chamada de onSmash
:
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('Reproduzindo!')}> Reproduzir Filme </Button> <Button onSmash={() => alert('Enviando!')}> Enviar Imagem </Button> </div> ); }
Neste exemplo, <button onClick={onSmash}>
mostra que a tag <button>
do navegador (minúsculo) ainda precisa de uma prop chamada onClick
. No entanto, é você quem escolhe o nome da prop recebida pelo seu componente personalizado Button
!
Quando seu componente oferece suporte a várias interações, você pode nomear as props dos manipuladores de eventos com base em conceitos específicos da sua aplicação. Por exemplo, o componente Toolbar
pode receber os manipuladores de eventos onPlayMovie
e onUploadImage
:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Reproduzindo!')} onUploadImage={() => alert('Enviando!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Reproduzir Filme </Button> <Button onClick={onUploadImage}> Enviar Imagem </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Note como o componente App
não precisa saber o que o componente Toolbar
fará com o onPlayMovie
ou onUploadImage
. Isso é um detalhe de implementação da Toolbar
. Aqui, a Toolbar
os passa como manipuladores onClick
para seus componentes Button
, mas posteriormente pode acioná-los também em um atalho de teclado. Nomear as props com base em interações específicas da aplicação, como onPlayMovie
, oferece a flexibilidade para alterar como elas são usadas no futuro.
Propagação de eventos
Os manipuladores de eventos também capturam eventos de quaisquer elementos filhos que o seu componente possa ter. Dizemos que um evento “borbulha” ou “se propaga” pela árvore: ele começa no local onde o evento ocorreu e, em seguida, se propaga pela árvore.
Esta <div>
contém dois botões, sendo que tanto a <div>
quanto cada botão tem seu próprio manipulador onClick
. Você sabe dizer quais manipuladores serão acionados quando clicar em um dos botões?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('Você clicou na toolbar!'); }}> <button onClick={() => alert('Reproduzindo!')}> Reproduzir Filme </button> <button onClick={() => alert('Enviando!')}> Enviar Imagem </button> </div> ); }
Se você clicar em qualquer um dos botões, o onClick
do botão clicado será executado primeiro e, em seguida, o onClick
da <div>
pai será executado. Como resultado, duas mensagens serão exibidas. Se você clicar na toolbar, apenas o onClick
da <div>
pai será executado.
Interrompendo a propagação
Os manipuladores de eventos recebem um event object como único argumento. Por convenção, ele é normalmente chamado de e
, que significa “event”, em inglês. Você pode usar esse objeto para obter informações sobre o evento.
Esse event object também permite que você interrompa a propagação. Caso deseje que um evento não chegue aos componentes pai, você precisa chamar e.stopPropagation()
como no exemplo do componente Button
abaixo:
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('Você clicou na toolbar!'); }}> <Button onClick={() => alert('Reproduzindo!')}> Reproduzir Filme </Button> <Button onClick={() => alert('Enviando!')}> Enviar Imagem </Button> </div> ); }
Ao clicar em um dos botões:
- O React chama o manipulador
onClick
passado para<button>
. - Esse manipulador, definido em
Button
, faz o seguinte:- Chama
e.stopPropagation()
, que impede que o evento continue se propagando. - Chama a função
onClick
, que é uma propriedade passada do componenteToolbar
.
- Chama
- Essa função, definida no componente
Toolbar
, exibe o alerta que foi definido no botão clicado. - Como a propagação foi interrompida, o manipulador
onClick
da<div>
pai não é executado.
Ao usar e.stopPropagation()
, agora somente um alerta (da tag <button>
) é exibido quando os botões são clicados, em vez de dois alertas (da tag <button>
e outro da <div>
do toolbar). Clicar em um botão não é a mesma coisa que clicar na div
da toolbar que envolve os botões, por isso, interromper a propagação faz sentido no caso dessa UI.
Deep Dive
Em casos raros, pode ser necessário capturar todos os eventos em elementos filhos, mesmo que eles tenham interrompido a propagação. Por exemplo, talvez você queira coletar cada clique para coleta de dados, independentemente da lógica de propagação. Você pode fazer isso adicionando Capture
no final do nome do evento:
<div onClickCapture={() => { /* Essa função é executada primeiro */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
Cada evento se propaga em três fases
- Ele se propaga para baixo, chamando todos os manipuladores
onClickCapture
. - Ele executa o manipulador
onClick
do elemento clicado. - Ele se propaga para cima, chamando todos os manipuladores
onClick
.
Os eventos de captura são úteis para códigos como roteadores ou análises, mas você provavelmente não os usará no código de uma aplicação.
Passando manipuladores como alternativa à propagação
Observe como esse manipulador de cliques executa uma linha de código e depois chama a prop onClick
passada pelo pai:
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
Você também pode adicionar mais código a esse manipulador antes de chamar o manipulador de eventos onClick
do elemento pai. Esse padrão fornece uma alternativa à propagação. Ele permite que o componente filho manipule o evento, mas ainda permitindo que o componente pai especifique algum comportamento adicional. Diferente da propagação, esse padrão não é automático, mas a sua vantagem é que você pode seguir claramente toda a cadeia de código executada como resultado de algum evento.
Caso você esteja dependendo da propagação de eventos e tenha dificuldade em rastrear quais manipuladores estão sendo executados e por quê, tente essa abordagem.
Removendo comportamento padrão
Alguns eventos do navegador têm um comportamento padrão associado a eles. Por exemplo, um evento de envio de formulário <form>
, que acontece quando um botão dentro dele é clicado, recarregará a página inteira por padrão:
export default function Signup() { return ( <form onSubmit={() => alert('Enviando!')}> <input /> <button>Enviar</button> </form> ); }
É possível chamar e.preventDefault()
no objeto do evento para impedir que isso aconteça:
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Enviando!'); }}> <input /> <button>Enviar</button> </form> ); }
Não confunda e.stopPropagation()
com e.preventDefault()
. Ambos são úteis, mas não estão relacionados:
e.stopPropagation()
impede que os manipuladores de eventos associados às tags superiores sejam acionados.e.preventDefault()
impede que o navegador execute o comportamento padrão associado a determinados eventos.
Os manipuladores de eventos podem ter efeitos colaterais?
Sem dúvida! Os manipuladores de eventos são o local ideal para efeitos colaterais.
Ao contrário das funções de renderização, os manipuladores de eventos não precisam ser puros, o que significa que é o local ideal para realizar modificações, por exemplo, alterar o valor de um input em resposta à digitação, ou alterar uma lista em resposta ao clique de um botão. No entanto, para alterar alguma informação, você primeiro precisa de uma maneira de armazenar essa informação. No React, isso é feito usando o state, a memória de um componente. Você aprenderá tudo sobre isso na próxima página.
Recap
- Você pode manipular eventos passando uma função como prop para um elemento como
<button>
. - Manipuladores de eventos devem ser passados, não chamados!
onClick={handleClick}
, e nãoonClick={handleClick()}
. - Você pode definir uma função de manipulador de eventos separado ou diretamente na prop.
- Os manipuladores de eventos são definidos dentro de um componente, para que possam acessar props.
- Você pode declarar um manipulador de eventos em um pai e passá-lo como prop para um filho.
- Você pode definir seus próprios manipuladores de eventos com nomes específicos para sua aplicação.
- Os eventos se propagam para cima. Use
e.stopPropagation()
no primeiro argumento para que isso não aconteça. - Os eventos podem ter um comportamento padrão do navegador que não são desejados. Use
e.preventDefault()
para remover esses comportamentos. - Chamar explicitamente uma prop de um manipulador de eventos a partir de um manipulador filho é uma boa alternativa à propagação.
Challenge 1 of 2: Corrigir um manipulador de eventos
Ao clicar neste botão, espera-se que o plano de fundo da página seja alternado entre branco e preto. No entanto, nada acontece quando você clica nele. Corrija o problema. (Não se preocupe com a lógica dentro do handleClick
— essa parte está ok.)
export default function LightSwitch() { function handleClick() { let bodyStyle = document.body.style; if (bodyStyle.backgroundColor === 'black') { bodyStyle.backgroundColor = 'white'; } else { bodyStyle.backgroundColor = 'black'; } } return ( <button onClick={handleClick()}> Alternar as luzes </button> ); }