O problema do recorte
Imagine um modal definido dentro de um card com overflow: hidden e
position: relative. Embora no React o modal seja "filho" do card, no DOM ele
ficaria recortado por esses estilos e preso no seu contexto de empilhamento
(z-index). É o caso típico de um dropdown que se corta ou um modal que aparece
atrás de outro elemento.
React.createPortal
Um Portal permite renderizar uns filhos em outro nó do DOM, fora da hierarquia visual do componente pai, sem deixar de pertencer à árvore do React:
import { createPortal } from "react-dom";
function Modal({ children }) {
return createPortal(
<div className="modal-overlay">{children}</div>,
document.body // é montado diretamente no <body>
);
}
createPortal(children, domNode) recebe o que renderizar e onde (um nó
do DOM já existente, tipicamente document.body ou um <div id="modal-root">).
A chave: o DOM se move, não a árvore do React
Embora o modal apareça no <body>, ele continua sendo filho de Modal na
árvore do React. Isso tem duas consequências importantes:
- O contexto se mantém: o componente portado lê os mesmos
Context, props e estado que se estivesse "no seu lugar". Ele não perde sua conexão com a app. - Os eventos borbulham pela árvore do React, não pelo DOM. Um
onClickdentro do portal borbulhará até os pais React doModal, mesmo que no DOM ele esteja em outro ramo.
Casos de uso típicos
- Modais e diálogos: são montados no
<body>para cobrir toda a tela. - Tooltips e menus flutuantes: evitam que
overflowouz-indexos recortem. - Notificações (toasts): vivem em uma camada própria acima de tudo.
Nota do ambiente: a validação de
createPortalnão é confiável no runtime de testes, por isso neste módulo o trabalhamos de forma conceitual.