3 - Портал

Portal

Чтобы продемонстрировать для чего нужен портал, реализуем стандартную задачу. Добавим в наше приложение header, который всегда будет в зафиксированном положение. Компонент Header.tsx отрисуем в App.tsx. В Header.tsx отрисуем компонент Cart.tsx

src/components/Header/Header.tsx
export const Header = () => {
  return (
    <div className={s.headerWrapper}>
      <div className={s.container}>
        <h3>logotype</h3>
        <Cart />
      </div>
    </div>
  )
}
Header.module.css
.headerWrapper {
  background-color: lightgray;
  margin-bottom: 20px;
  position: fixed;
  height: 60px;
  left: 0;
  right: 0;
  top: 0;
  width: 100%;
  display: flex;
  align-items: center;
}
 
.container {
  display: flex;
  width: 1440px;
  justify-content: space-between;
  margin: 0 auto;
  align-items: center;
}

И модалка сломалась 😢. Почему так ?

modal without portal

Потому что сейчас наша модалка рендерится в том месте где мы указали.

💡

Элемент с position: absolute позиционируется относительно ближайшего предка с заданным позиционированием, то есть с position: relative, position: absolute, position: fixed или position: sticky. Если такой предок не найден, элемент позиционируется относительно всего документа (то есть относительно body или html).

modal without portal dev

modal without portal dev 2

Portal

React-порталы позволяют рендерить компонент за пределами его родительского DOM-дерева. Это особенно полезно для элементов, которые должны отображаться поверх остальной части интерфейса, например, для модальных окон, всплывающих сообщений и других overlay-компонентов. Порталы помогают избежать проблем с CSS-стилями, z-index, и контекстом, которые могут возникать, если модальное окно находится внутри DOM-дерева родительского компонента.

Как работают порталы

Порталы создаются с помощью функции ReactDOM.createPortal, которая принимает два аргумента:

  1. React-элемент — то, что нужно отобразить.
  2. DOM-узел — контейнер, куда будет рендериться элемент, например, document.body или любой другой элемент, находящийся вне основного приложения.

Преимущества порталов

  • Упрощенное управление z-index: Поскольку модалка рендерится в #modal-root вне основного дерева, легче управлять её позиционированием и наложением.

  • Избежание конфликтов CSS: Стили модального окна не будут взаимодействовать с родительскими элементами.

  • Улучшение пользовательского опыта: Легче управлять событиями, например, кликами за пределами модального окна для его закрытия.

Modal.tsx
export const Modal = ({ onClose, open, children, modalTitle }: Props) => {
  return (
    <>
      {open && (
        <>
          {createPortal(
            <div className={s.overlay}>
              <div className={s.content}>
                <h3 className={s.title}>{modalTitle}</h3>
                <hr />
                {children}
                <button className={s.closeIcon} onClick={onClose}>
                  x
                </button>
              </div>
            </div>,
            document.body
          )}
        </>
      )}
    </>
  )
}

Результат. Модалка снова отрабатывает верно, т.к. рендерится не внутри header, а в body. И теперь мы уверены, что у модалки никогда не сломаются стили 🚀

modal-with-portal