Znamy już dostatecznie dużo Reacta, JSa oraz HTMLa, aby ogarnąć komponent Reacta sterujący HTMLDialogElement. Kontynuacja lekcji poprzednich, do dzieła.
Oba przykłady pochodzą z Githuba Academind, który już kilka razy sobie omawialiśmy.
Prosty modal:
import { useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';
function Modal({ open, children, onClose }) {
const dialog = useRef();
useEffect(() => {
if (open) {
dialog.current.showModal();
} else {
dialog.current.close();
}
}, [open]);
return createPortal(
<dialog className="modal" ref={dialog} onClose={onClose}>
{open ? children : null}
</dialog>,
document.getElementById('modal')
);
}
export default Modal;
Co my tu mamy:
- RFC, który przyjmuje propsy za pomocą dekompozycji obiektu
- Ref, czyli zmienna, która zostanie utworzona jeden raz i potencjalnie można ją z DOM używać (i jest tak używana tutaj)
- useEffect, który ruszy na mount render i na diff rendery jeżeli zmieni się prop open
- ref dołączony do HTMLDialogElement
- Conditional rendering dzieci w dialogu, nieco bez sensu, bo te dzieci są widoczne tylko gdy dialog jest otwarty, ale niech będzie, że w zbadaj element nikt nie podejrzy co ten modal ma zanim się nie pojawi
- useEffect otwiera z tłem bądź zamyka dialog metodami showModal i close HTMLDialogElement, na który wskazuje ref
- Mamy portal, który gdzieś w index.html znajdzie element o id modal i tam wyrenderuje ten RFC
- RFC nadal pozostaje w swojej hierarchii komponentów, jego rodzic to jego rodzic, nadal jest używalny (useEffect i inne Reactowe rzeczy działają), po prostu na poziomie wyrenderowania renderuje się poza aplikacją
Ok, z tego samego Githuba, lepszy komponent:
import { forwardRef, useImperativeHandle, useRef } from 'react';
import { createPortal } from 'react-dom';
const ResultModal = forwardRef(function ResultModal(
{ targetTime, remainingTime, onReset },
ref
) {
const dialog = useRef();
const userLost = remainingTime <= 0;
const formattedRemainingTime = (remainingTime / 1000).toFixed(2);
const score = Math.round((1 - remainingTime / (targetTime * 1000)) * 100);
useImperativeHandle(ref, () => {
return {
open() {
dialog.current.showModal();
},
};
});
return createPortal(
<dialog ref={dialog} className="result-modal">
{userLost && <h2>You lost</h2>}
{!userLost && <h2>Your Score: {score}</h2>}
<p>
The target time was <strong>{targetTime} seconds.</strong>
</p>
<p>
You stopped the timer with{' '}
<strong>{formattedRemainingTime} seconds left.</strong>
</p>
<form method="dialog" onSubmit={onReset}>
<button>Close</button>
</form>
</dialog>,
document.getElementById('modal')
);
});
export default ResultModal;
Co mamy:
- Nie ma żadnego useEffect, komponent ma się pojawić (w dodatku poza root apki) i zniknąć a sterować ma tym komponent wyżej
- ForwardRef, czyli rodzic tworzy ref i przekazuje temu RFC
- Ten RFC na potrzeby enkapsulacji tworzy prywatne ref i przypisuje do HTMLDialogElement (tak, znam te nazwy interfejsów, to nic trudnego akurat)
- Ten „publiczny” ref od rodzica jest forwardowany do useImperativeHandle, gdzie mamy publiczną metodę dla rodzica, aby sobie otworzył modal, wewnątrz tej metody korzystamy z „prywatnego” refa
- Mamy jakieś pseudo stałe. Nie są współdzielone przez wszystkie RFC, ale też i o tym należy pamiętać, każdy render i re-render to jest nowe zdefiniowanie pod nową referencją, to tak uprzedzająco, mam nadzieję, że tego nie zapomnieliśmy
- Na zamknięcie modalu autor akurat wykorzystał sprytnie form method dialog a w onSubmit wrzucił onReset, które bierze z propsów, dobry kod i bardziej modularny i jak znamy HTMLDialogElement, to nic nas tu nie zdziwi.
Ok, więcej Reacta niedługo, mam nadzieję, że te przykłady pokazują, że naprawdę TRZEBA bardzo, bardzo dobrze znać „normalny” JS, HTML, no ten cały MDN plus być na bieżąco z nowościami.
Inaczej jakakolwiek nauka bądź praca to mordęga, w dodatku nie wiemy co jest Reactem a co JSem i naprawdę, ciężko jest. Nie ma drogi na skróty.