Eventos do Mouse
Lide com cliques, cliques duplos, hovers e movimento do mouse em componentes React.
Busque em todas as páginas da documentação
Lide com cliques, cliques duplos, hovers e movimento do mouse em componentes React.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
| Prop do React | Tipo TypeScript | Dispara Quando |
|---|---|---|
onClick | React.MouseEvent<T> | Elemento é clicado (mousedown + mouseup no mesmo alvo) |
onDoubleClick | React.MouseEvent<T> | Elemento é clicado duas vezes |
onMouseDown | React.MouseEvent<T> | Botão do mouse é pressionado no elemento |
onMouseUp | React.MouseEvent<T> | Botão do mouse é liberado no elemento |
onMouseEnter | React.MouseEvent<T> | Ponteiro entra no elemento (NÃO borbulha) |
onMouseLeave | React.MouseEvent<T> | Ponteiro sai do elemento (NÃO borbulha) |
onMouseOver | React.MouseEvent<T> | Ponteiro entra no elemento ou em um filho (borbulha) |
onMouseOut | React.MouseEvent<T> | Ponteiro sai do elemento ou entra em um filho (borbulha) |
onMouseMove | React.MouseEvent<T> | Ponteiro se move enquanto está sobre o elemento |
onContextMenu | React.MouseEvent<T> | Botão direito do mouse ou tecla de menu de contexto é pressionado |
Cartão de receita de referência rápida -- pronto para copiar e colar.
// Handler de clique com evento tipado
function ClickExample() {
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log("Clicado em", e.clientX, e.clientY);
};
return <button onClick={handleClick}>Clique em mim</button>;
}
// Hover com onMouseEnter / onMouseLeave
function HoverExample() {
const [hovered, setHovered] = React.useState(false);
return (
<div
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{ background: hovered ? "#e0e7ff" : "#fff" }}
>
Passe o mouse sobre mim
</div>
);
}
// Menu de contexto com clique direito
function ContextMenuExample() {
const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
console.log("Menu customizado em", e.clientX, e.clientY);
};
return <div onContextMenu={handleContextMenu}>Clique com o botão direito em mim</div>;
}
// Clique duplo
function DoubleClickExample() {
const handleDoubleClick = (e: React.MouseEvent<HTMLSpanElement>) => {
console.log("Clique duplo");
};
return <span onDoubleClick={handleDoubleClick}>Clique duas vezes para editar</span>;
}Quando usar isso: Você precisa responder a interações do mouse -- cliques, hovers, cliques direitos ou rastreamento de posição -- em um componente cliente.
"use client";
import { useState, useRef, useCallback } from "react";
type ContextMenuState = {
visible: boolean;
x: number;
y: number;
};
export default function InteractiveCard() {
const [hovered, setHovered] = useState(false);
const [clickCount, setClickCount] = useState(0);
const [contextMenu, setContextMenu] = useState<ContextMenuState>({
visible: false,
x: 0,
y: 0,
});
const cardRef = useRef<HTMLDivElement>(null);
const handleClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
// Fecha o menu de contexto em clique regular
if (contextMenu.visible) {
setContextMenu((prev) => ({ ...prev, visible: false }));
return;
}
setClickCount((c) => c + 1);
},
[contextMenu.visible]
);
const handleContextMenu = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
const rect = cardRef.current?.getBoundingClientRect();
if (!rect) return;
setContextMenu({
visible: true,
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
},
[]
);
const handleMenuAction = useCallback((action: string) => {
console.log("Ação do menu:", action);
setContextMenu((prev) => ({ ...prev, visible: false }));
}, []);
return (
<div
ref={cardRef}
onClick={handleClick}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => {
setHovered(false);
setContextMenu((prev) => ({ ...prev, visible: false }));
}}
onContextMenu={handleContextMenu}
style={{
position: "relative",
padding: "24px",
border: `2px solid ${hovered ? "#6366f1" : "#e5e7eb"}`,
borderRadius: "12px",
background: hovered ? "#f5f3ff" : "#fff",
transition: "all 150ms ease",
cursor: "pointer",
userSelect: "none",
}}
>
<h3>Cartão Interativo</h3>
<p>Clicado {clickCount} vezes. Clique com o botão direito para o menu de contexto.</p>
{contextMenu.visible && (
<ul
style={{
position: "absolute",
top: contextMenu.y,
left: contextMenu.x,
background: "#fff",
border: "1px solid #e5e7eb",
borderRadius: "8px",
boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
listStyle: "none",
padding: "4px 0",
margin: 0,
zIndex: 10,
}}
>
{["Editar", "Duplicar", "Excluir"].map((action) => (
<li
key={action}
onClick={(e) => {
e.stopPropagation();
handleMenuAction(action);
}}
onMouseEnter={(e) =>
(e.currentTarget.style.background = "#f3f4f6")
}
onMouseLeave={(e) =>
(e.currentTarget.style.background = "transparent")
}
style={{ padding: "8px 16px", cursor: "pointer" }}
>
{action}
</li>
))}
</ul>
)}
</div>
);
}O que isso demonstra:
onClick, onMouseEnter, onMouseLeave e onContextMenu em um único elementogetBoundingClientRecte.stopPropagation() para evitar que cliques em itens do menu acionem o handler de clique do cartãoe.preventDefault() em onContextMenu para suprimir o menu padrão do navegadorReact.MouseEvent<T> que envolve o MouseEvent nativo com uma interface consistente entre navegadores.onMouseEnter e onMouseLeave NÃO borbulham. Eles disparam apenas para o elemento exato, não para seus filhos. Use onMouseOver e onMouseOut se precisar de comportamento de borbulhamento.setTimeout), leia os valores que você precisa em variáveis locais primeiro.Clique com passagem de dados:
type Item = { id: string; name: string };
function ItemList({ items }: { items: Item[] }) {
const handleClick = (item: Item) => (e: React.MouseEvent<HTMLLIElement>) => {
console.log("Selecionado:", item.id, "em", e.clientX);
};
return (
<ul>
{items.map((item) => (
<li key={item.id} onClick={handleClick(item)}>
{item.name}
</li>
))}
</ul>
);
}Rastreamento de hover com coordenadas:
function HoverTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
setPosition({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
},
[]
);
return (
<div onMouseMove={handleMouseMove} style={{ width: 300, height: 300 }}>
Cursor em ({position.x}, {position.y})
</div>
);
}Detecção de início de arrastar (mousedown + limite de mousemove):
function DragDetector() {
const startPos = useRef<{ x: number; y: number } | null>(null);
const [dragging, setDragging] = useState(false);
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
startPos.current = { x: e.clientX, y: e.clientY };
};
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
if (!startPos.current) return;
const dx = e.clientX - startPos.current.x;
const dy = e.clientY - startPos.current.y;
if (Math.sqrt(dx * dx + dy * dy) > 5) {
setDragging(true);
}
};
const handleMouseUp = () => {
startPos.current = null;
setDragging(false);
};
return (
<div
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
{dragging ? "Arrastando..." : "Clique e arraste"}
</div>
);
}Detecção de pressionamento longo:
function LongPressButton({ onLongPress }: { onLongPress: () => void }) {
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleMouseDown = () => {
timerRef.current = setTimeout(onLongPress, 600);
};
const handleMouseUp = () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
return (
<button onMouseDown={handleMouseDown} onMouseUp={handleMouseUp}>
Pressione e segure
</button>
);
}Menu de contexto com clique direito e portal:
import { createPortal } from "react-dom";
function ContextMenuPortal({
x,
y,
onClose,
}: {
x: number;
y: number;
onClose: () => void;
}) {
return createPortal(
<div
style={{ position: "fixed", top: y, left: x, zIndex: 9999 }}
onClick={onClose}
>
<ul style={{ background: "#fff", border: "1px solid #ddd", padding: 8 }}>
<li>Copiar</li>
<li>Colar</li>
</ul>
</div>,
document.body
);
}// O genérico T especifica o tipo do elemento para e.currentTarget
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget; // HTMLButtonElement -- o elemento ao qual o handler está anexado
e.target; // EventTarget -- o elemento real clicado (pode ser um filho)
};
// Use HTMLElement como um fallback genérico quando o elemento exato varia
const handleAny = (e: React.MouseEvent<HTMLElement>) => {
e.currentTarget.dataset.id; // funciona para qualquer elemento HTML
};
// Estreitando e.target (é tipado como EventTarget, não Element)
const handleDelegated = (e: React.MouseEvent<HTMLUListElement>) => {
const target = e.target as HTMLElement;
if (target.tagName === "LI") {
console.log(target.textContent);
}
};
// Detecção de botão do mouse
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
// e.button: 0 = esquerdo, 1 = meio, 2 = direito
if (e.button === 2) console.log("Clique direito");
};
// Coordenadas disponíveis em cada evento do mouse
// e.clientX / e.clientY -- relativo à viewport
// e.pageX / e.pageY -- relativo ao documento
// e.screenX / e.screenY -- relativo ao monitor
// e.nativeEvent.offsetX / offsetY -- relativo ao elemento alvoonMouseEnter/onMouseLeave vs onMouseOver/onMouseOut -- onMouseEnter e onMouseLeave NÃO borbulham e NÃO disparam ao mover entre elementos filhos. onMouseOver e onMouseOut BORBULHAM, então eles disparam ao entrar/sair de filhos também. Use o par errado e os estados de hover piscarão em elementos aninhados.
Criando novas funções de seta no JSX a cada renderização -- Escrever onClick={() => handleClick(id)} cria uma nova referência de função a cada renderização, o que pode causar re-renderizações desnecessárias em componentes filhos que dependem de igualdade referencial. Correção: Use useCallback ou extraia o handler para uma referência estável. Para listas, considere um componente filho que recebe o item e anexa seu próprio handler.
onClick dispara com QUALQUER botão do mouse por padrão -- O onClick do React dispara apenas para cliques esquerdos na maioria dos navegadores, mas isso não é garantido para todos os elementos e navegadores. Se você precisar distinguir botões, verifique e.button em onMouseDown ou onMouseUp.
Confusão entre e.target e e.currentTarget -- e.target é o elemento que foi realmente clicado (pode ser um filho aninhado). e.currentTarget é o elemento ao qual o handler está anexado. Se você clicar em um <span> dentro de um <button>, e.target é o span, e.currentTarget é o botão. Acessar propriedades como .value ou .dataset em e.target requer estreitamento de tipo.
onDoubleClick dispara dois eventos onClick primeiro -- Um clique duplo dispara: onClick -> onClick -> onDoubleClick. Se o seu handler onClick realizar uma ação, você obterá essa ação duas vezes antes que o clique duplo dispare. Correção: Use um timer de debounce para distinguir cliques únicos de duplos, ou evite combinar ambos no mesmo elemento.
Eventos sintéticos são anulados após o handler -- Acessar e.clientX dentro de um setTimeout ou após um await retorna null porque o React recicla o objeto do evento. Correção: Leia os valores em variáveis locais antes de qualquer operação assíncrona: const x = e.clientX;.
onMouseMove dispara com muita frequência -- Anexar atualizações de estado a onMouseMove pode causar centenas de re-renderizações por segundo. Correção: Use throttle com requestAnimationFrame ou uma utilidade de throttle, ou use um ref para armazenar a posição e renderizar apenas quando necessário.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Eventos de Ponteiro (onPointerDown, etc.) | Você precisa suportar toque, caneta e mouse uniformemente | Você visa apenas o mouse em desktop |
CSS :hover | Apenas estilos de hover, nenhuma lógica JS necessária | Você precisa rastrear o estado de hover ou coordenadas em JS |
API de Arrastar e Soltar (onDragStart, etc.) | Você precisa de arrastar e soltar completo com transferência de dados | Você só precisa de detecção de movimento de clique e arraste |
addEventListener em useEffect | Você precisa de listeners globais (por exemplo, rastreamento de mouse em nível de documento) | O evento está restrito a um elemento específico |
| Bibliotecas de terceiros (dnd-kit, Framer Motion) | Arrastar complexo, gestos ou animação vinculados à posição do mouse | Handlers simples de clique ou hover |
onMouseEnter / onMouseLeave NÃO borbulham e disparam apenas para o elemento exato, não para seus filhosonMouseOver / onMouseOut BORBULHAM, então disparam ao entrar/sair de elementos filhos tambémEnter/Leave para estado de hover em um único elemento; use Over/Out quando precisar de borbulhamentoe.target é o elemento que foi realmente clicado (pode ser um filho aninhado)e.currentTarget é o elemento ao qual o handler está anexado<span> dentro de um <button>, e.target é o span, e.currentTarget é o botãoUse e.button em onMouseDown ou onMouseUp:
0 = botão esquerdo1 = botão do meio2 = botão direitoNão confie em onClick para detecção de botão -- ele é primariamente para cliques esquerdos.
const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault(); // suprime o menu padrão do navegador
setMenu({ visible: true, x: e.clientX, y: e.clientY });
};
return <div onContextMenu={handleContextMenu}>Clique com o botão direito em mim</div>;Um clique duplo dispara: onClick -> onClick -> onDoubleClick. Se o seu handler onClick realiza uma ação, ele é executado duas vezes antes que o clique duplo dispare. Use um timer de debounce para distinguir cliques únicos de duplos, ou evite combinar ambos os handlers no mesmo elemento.
Os eventos sintéticos do React são anulados após o retorno do handler. Acessar propriedades posteriormente retorna null. Leia os valores em variáveis locais antes de qualquer operação assíncrona: const x = e.clientX;.
function ItemList({ items }: { items: Item[] }) {
const handleClick = (item: Item) =>
(e: React.MouseEvent<HTMLLIElement>) => {
console.log("Selecionado:", item.id);
};
return (
<ul>
{items.map((item) => (
<li key={item.id} onClick={handleClick(item)}>{item.name}</li>
))}
</ul>
);
}Para melhor desempenho, extraia cada item para um componente filho com seu próprio handler estável.
onMouseMove dispara com muita frequência (centenas de vezes por segundo). Anexar atualizações de estado a ele causa re-renderizações excessivas. Use throttle com requestAnimationFrame, uma utilidade de throttle, ou armazene a posição em um ref e renderize apenas quando necessário.
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleMouseDown = () => {
timerRef.current = setTimeout(onLongPress, 600);
};
const handleMouseUp = () => {
if (timerRef.current) clearTimeout(timerRef.current);
};clientX / clientY -- relativo à viewportpageX / pageY -- relativo ao documento (inclui deslocamento de rolagem)screenX / screenY -- relativo ao monitornativeEvent.offsetX / offsetY -- relativo ao elemento alvo// Tipo de elemento específico
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.currentTarget; // HTMLButtonElement
};
// Fallback genérico para qualquer elemento HTML
const handleAny = (e: React.MouseEvent<HTMLElement>) => {
e.currentTarget.dataset.id; // funciona para qualquer elemento
};const handleDelegated = (e: React.MouseEvent<HTMLUListElement>) => {
// e.target é tipado como EventTarget, não Element
const target = e.target as HTMLElement;
if (target.tagName === "LI") {
console.log(target.textContent);
}
};Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥