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.