Portale w React są funkcjonalnością, na którą natkniesz się prawdopodobnie na samym końcu – po przekopaniu całego internetu w poszukiwaniu rozwiązania. Renderowanie zawartości w innym miejscu niż logicznie element należy wydaje się nie mieć sensu – pomaga jednak rozwiązać problemy z z-index czy overflow na jakie możesz natknąć się przy próbach wyświetlenia treści. Choć Portal niekoniecznie będzie pierwszym wyborem – często może okazać się znacznie szybszym rozwiązaniem niż refaktor lub inne bardziej oczywiste opcje – poznajmy więc jak one działają.
React Portals
Portale pozwalają wyświetlić element w podanym rodzicu, który może być zlokalizowany w dowolnym miejscu. Aby móc to wykorzystać stwórzmy w pliku index.html element ulokowany poza rootem głównej aplikacji Reacta. Dla przypomnienia, domyślnie aplikacja CRA zawiera index.html z div z id="root", w którym wyrenderuje się aplikacja. Dodajmy element poza głównym rootem oraz kolory, które pozwolą rozróżnić oba elementy:
W kolejnym kroku stworzymy OutsideToastsComponent zawierający „toasty”, komponent umieścimy wewnątrz div na którym przypniemy wyświetlanie alertu na kliknięcie – by pokazać, że propagacja eventów w górę działa poprawnie mimo że elementy nie będą w drzewie DOM tego elementu:
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div
onClick={() => alert('I was clicked! Even from outside my DOM which means event propagates properly')}>
<OutsideToastsComponent/>
</div>
<div
onClick={() => alert('I was clicked! Even from outside my DOM which means event propagates properly')}>
<OutsideToastsComponent />
</div>
<div
onClick={() => alert('I was clicked! Even from outside my DOM which means event propagates properly')}>
<OutsideToastsComponent />
</div>
Sam komponent będzie zawierał standardowe elementy stąd nie będziemy całego tu wklejać – kod na Github i przykład klikalny na Github Pages będą dostępne – warto natomiast zwrócić uwagę na to, co jest zwracane:
To, co w normalnym kodzie byłoby zwrócone bezpośrednio, jest przekazane jako pierwszy parametr do funkcji createPortal. Drugim parametrem jest miejsce gdzie elementy mają zostać wyrenderowane.
I faktycznie, elementy renderują się poza głównym drzewem aplikacji czyli div z id="root":
Natomiast jeśli chodzi o podgląd drzewa komponentów React, komponent nasz pokazuje się w drzewie aplikacji:
Alternatywy dla React Portals
Alternatywnym podejściem do powyższego jest pozostawienie toastów w aplikacji głównej w normalnym flow Reactowym, ale umieszczenie komponentu z nimi niezbyt głęboko w drzewie komponentów.
Z pomocą w implementacji takiego podejścia pomoże nam globalne zarządzanie stanem. Rozważymy tutaj dwie opcje:
React Context
React Redux
React Context
React Context pokrótce przedstawiłem w jednym ze swoich postów:
Będzie domyślnie używał biblioteki Redux Toolkit. Biblioteka ta pochodzi od twórców Reacta i znacząco usprawnia pracę z nim. Jeśli chcesz poznać Redux Toolkit lepiej, kliknij w poniższy obrazek:
Samo pisane kodu rozpocznijmy od dodania slice, który będzie obsługiwał stan:
Sam kod komponentu, który jest prosty – zawiera dispatchowanie innych akcji i wyświetlenie listy – dostępny będzie na Github.
Zanim się rozstaniemy – na potrzeby tego przykładu użyłem hooka useSelector – w pracy jednak nie przechodzimy na razie z connect na useSelector mimo, ze wersje bibliotek pozwalają. Dlaczego? Ponieważ niesie to ze sobą kilka zagrożeń, które opisałem w artykule:
Choć Portale nie wydają się być rozwiązaniem pierwszego wyboru, warto zdawać sobie sprawę z ich istnienia. Choć do problemów które rozwiązują można podejść inaczej – na przykład za pomocą globalnego stanu – mogą pomóc w sytuacji, gdy inne rozwiązania będą kosztowne.