W React, umiejscowienie komponentu w strukturze DOM zazwyczaj zależy od miejsca jego wywołania w kodzie. Z reguły komponent jest umieszczany dokładnie tam, gdzie go zdefiniowaliśmy. Jednak czasami pojawia się potrzeba, aby komponent wyrenderował swój kod JSX w innym miejscu niż to, w którym został wywołany. Przykładem może być sytuacja, gdy chcemy wyświetlić błędy formularza w specyficznym miejscu strony. W takich przypadkach nadal chcemy zachować deklaratywny charakter Reacta, czyli zamiast bezpośrednio manipulować DOM-em, po prostu informujemy Reacta, gdzie chcemy, aby nasz komponent został wyrenderowany.
Jak używać portali?
Aby wykorzystać portale, dodajemy w pliku index.html
element DOM, do którego chcemy przenieść wyrenderowaną zawartość. Przykładowo, możemy zmodyfikować standardowy plik index.html
wygenerowany przez create-react-app
, dodając element o id modal-root
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
....
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="modal-root"></div> <!-- Tutaj dodaliśmy nowy element -->
</body>
</html>
Następnie, w naszej aplikacji React, używamy createPortal
w pliku Modal.js
, aby wyrenderować zawartość modalu w elemencie modal-root
.
plik App.js
import React, { useState } from 'react';
import Modal from './Modal';
function App() {
const [isModalOpen, setModalOpen] = useState(false);
return (
<div>
<h1>Kliknij poniżej, aby otworzyć modal:</h1>
<button onClick={() => setModalOpen(true)}>Otwórz Modal</button>
{isModalOpen && (
<Modal onClose={() => setModalOpen(false)}>
<p>To jest treść modalu!</p>
<button onClick={() => setModalOpen(false)}>Zamknij Modal</button>
</Modal>
)}
</div>
);
}
export default App;
Oraz nasz plik Modal.js
, w którym zastosowałem createPortal
import React from 'react';
import { createPortal } from 'react-dom';
const Modal = ({ onClose, children }) => {
return createPortal(
<div style={{position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: 'white', padding: '20px', zIndex: 1000}}>
{children}
<button onClick={onClose}>Zamknij</button>
</div>,
document.getElementById('modal-root')
);
}
export default Modal;
W tym przykładzie, zawartość modalu jest renderowana przy użyciu createPortal
w elemencie DOM o id modal-root
. Dzięki temu modal może „wyskoczyć” poza zwykłą hierarchię renderowania komponentów React, co pozwala na umieszczenie go w dowolnym miejscu strony. Jest to szczególnie przydatne dla komponentów takich jak modale, które wizualnie chcemy oddzielić od reszty aplikacji.
Kiedy używać Portali?
Portale znajdują swoje zastosowanie w kilku typowych przypadkach:
- Modale, lightboxy, tooltipy: Gdy chcemy, aby elementy te były renderowane w korzeniu dokumentu, aby uniknąć problemów z CSS (np. z
overflow
lubz-index
). - Dostępność (a11y): Przenosząc modale do korzenia DOM, możemy łatwiej zarządzać fokusem i nie przerywać sekwencji czytania ekranu dla użytkowników korzystających z czytników ekranu.
- Unikanie konfliktów z CSS: Kiedy komponenty muszą być „wyrwane” z obecnego kontekstu, aby uniknąć problemów z dziedziczeniem stylów lub niewłaściwym pozycjonowaniem.