Poznajemy dość ciekawy custom hook, z Githuba, który już nieco sobie omawialiśmy. Moim zdaniem całkiem ładny, do dzieła!
Ok, najpierw jego użycie:
function Search({ query, setQuery }) {
const inputEl = useRef(null);
useKey("Enter", function () {
if (document.activeElement === inputEl.current) return;
inputEl.current.focus();
setQuery("");
});
return (
<input
className="search"
type="text"
placeholder="Search movies..."
value={query}
onChange={(e) => setQuery(e.target.value)}
ref={inputEl}
/>
);
}
Co my tu widzimy:
- RFC przyjmuje query i setQuery jako propsy
- Po nazewnictwie możemy domyślić się, że chodzi o state i setState rodzica przekazane z góry w dół czyli w jednym słusznym kierunku
- Mamy tam refa, null
- Ref jest przypisany do elementu input z JSX
- Na onChange używamy setQuery z propsów i wrzucamy tekst, który jest w inpucie
- query, zmienna state rodzica, jest zaś przypisana do value
- Hook useKey słucha na enter
- Jeżeli activeElement to nasz input to nic nie robimy
- Jeżeli enter był gdzieś indziej, to robimy focus elementowi
- I ustawiamy query na pusty string
Możemy to wszystko robić z dwóch powodów:
- element input to jest element markupu tego komponentu a nie inny komponent – wtedy potrzebowalibyśmy forwardRef
- ref jest do elementu JSX, a nie komponentu, który mógłby mieć useImperativeHandle i wtedy też możemy jedynie wywoływać te metody, które nam wolno
Ok, ale sam hook:
import { useEffect } from "react";
export function useKey(key, action) {
useEffect(
function () {
function callback(e) {
if (e.code.toLowerCase() === key.toLowerCase()) {
action();
}
}
document.addEventListener("keydown", callback);
return function () {
document.removeEventListener("keydown", callback);
};
},
[action, key]
);
}
Swoją drogą action podane jako callback. Gdybyśmy tak utworzyli sobie definicję action w RFC Search to byłaby definiowana przy każdym re-renderze z nową referencją, co triggerowałoby tablicę zależności i bam, pętla nieskończona.
Jak widać callback do hooka nie ma tego problemu, nie wiedziałem szczerze, że z custom hookami też tak to działa. Gdyby był problem – zawsze można wynieść definicję powyżej poziomu RFC albo do innego pliku JS i zaimportować stamtąd (powyżej poziomu RFC).
To by było na tyle na razie.