React Hooks: Piszemy własne hooki!

Ten wpis jest 41 częścią z 41 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Niewątpliwą zaletą React Hooks jest to, jak łatwo możemy wydzielać fragmenty logiki do własnych hooków. W tym artykule pokazuję Ci, jak napisać swoje hooki, jakie obowiązują zasady i jak sprawić, aby Twój kod był lepszy!

Własny React Hook

Przy tworzeniu własny hooków obowiązują nas te same reguły, co tych wbudowanych: nazwa każdego hooka musi zaczynać się od „use”. Hook jest zwykłą funkcją, a w środku niej możemy wywoływać inne funkcje! Dzięki temu kompozycja wielu hooków staje się bajecznie prosta i nie wymaga żadnych skomplikowanych technik. To tylko zwykłe funkcje.

useDocumentTitle

Zacznijmy od czegoś prostego: Hook, który zmienia tytuł strony na podany. Dla przypomnienia, w artykule na temat useEffect zaimplementowaliśmy to w ten sposób:

useEffect(() => {
  document.title = `Nowy tytuł!`
});

Jest to bardzo prosta, żeby nie powiedzieć naiwna implementacja, ale na pewno spełnia swoje zadanie. Jak stworzyć z niej własny hook? O tak:

const useDocumentTitle = title => {  
  useEffect(() => {
    document.title = title;
  }, [title]);
}

Następnie w komponencie użyjemy go w taki sposób:

useDocumentTitle('Mój tytuł');

Łał, to było proste, no nie? Stworzyliśmy zwykłą funkcję, w której wywołujemy hook i to tyle.

Dodajmy coś jeszcze, np. przywracanie oryginalnego tytułu, gdy komponent jest odmontowywany:

const useDocumentTitle = title => {  
  const defaultTitle = useRef(document.title); // 1

  useEffect(() => {
    document.title = title;;
  }, [title]);

  useEffect(() => { // 2
    return () => {
      document.title = defaultTitle.current;
    }
  }, []);
}

Tutaj w linijce oznaczonej numerem 1 zapisujemy istniejący document.title do refa. Następnie w drugim useEffect zwracamy funkcję, która zostanie wywołana tylko przy odmontowywaniu komponentu i ustawiamy w niej document.title na oryginalną wartość zapisaną w refie.

usePrevious

Czasem potrzebne nam informacje o poprzedniej wartości danego propsa. O ile w klasach nie było z tym problemu, tak w komponentach funkcyjnych musimy zadbać o to już sami:

const usePrevious = (value) => {
  const ref = useRef(); // 1

  useEffect(() => {
    ref.current = value; // 3
  }, [value]);

  return ref.current; // 2
};

W tym przypadku tworzymy pusty ref (1), zwracamy poprzednią wartość (2), a następnie zapisujemy do refa nową(3). Dzieje się tak dlatego, że useEffect uruchamia się asynchronicznie.

Najczęściej jednak zamiast używać usePrevious, możemy ten sam problem rozwiązać inaczej i prościej, np. poprzez dodanie danego propsa do tablicy zależności useEffect. Wtedy React sam za nas porówna starą i nową wartość!

useApi

A gdyby tak… w hooku zamknąć pobieranie danych z API? To proste! Weźmy kod podobny do tego z artykułu o Hookach i API i zamieńmy na własny hook, którego będziemy mogli używać w wielu miejscach w naszej aplikacji. Pierwsze podejście wygląda tak:

const useApi = (path) => {
  const [response, setResponse] = useState({ data: null, isLoading: true, error: null });

  useEffect(() => {
    setResponse({ data: null, isLoading: true, error: null });
    fetch('https://rickandmortyapi.com/api/' + path)
      .then(res => res.json())
      .then((data) => setResponse({ data, isLoading: false, error: null }))
      .catch((error) => setResponse({ data: null, isLoading: false, error }));
  }, [path, setResponse]);

  return response;
};

Nie jest to jeszcze zbyt piękne, ale działa całkiem nieźle:

function App() {
  const [page, setPage] = React.useState(10);
  const { data, isLoading } = useApi(`character/?page=${page}`);

  return (
    <>
      {isLoading && 'Loading…'}
      <button onClick={() => setPage((p) => p + 1)}>Next</button>
      <ul>
        {data?.results.map((character) => (
          <li key={character.id}>{character.name}</li>
        ))}
      </ul>
    </>
  );
}

Naszego hooka możemy poprawić na dwa sposoby. Po pierwsze, pozbyć się z niego logiki odpowiedzialnej za pobieranie danych z API – tzn. jest to coś całkowicie niezależnego od Reacta. Chcemy wywołać tylko getData(…) i tyle, a nie martwić się o jakieś res.json() i inne podobne historie. Przykładowo (upraszczając):

const doFetch = async (path) => {
  const res = await fetch('https://rickandmortyapi.com/api/' + path);
  if (res.ok) {
    return res.json();
  }
  throw await res.json();
};

Drugą poprawką będzie użycie useReducer, aby pozbyć się nadmiaru kodu z samego useEffect:

const apiReducer = (state, action) => {
  switch (action.type) {
    case 'FETCHING':
      return { data: null, isLoading: true, error: null };
    case 'SUCCESS':
      return { data: action.payload, isLoading: false, error: null };
    case 'ERROR':
      return { data: null, isLoading: false, error: action.payload };
  }
  return state;
};

const useApi = (path) => {
  const [response, dispatch] = useReducer(apiReducer, { data: null, isLoading: false, error: null });

  useEffect(() => {
    dispatch({ type: 'FETCHING' });
    doFetch(path)
      .then((data) => dispatch({ type: 'SUCCESS', payload: data }))
      .catch((error) => dispatch({ type: 'ERROR', payload: error }));
  }, [path]);

  return response;
};

Kod jest znacznie dłuższy, ale wydaje mi się też bardziej czytelny, bo oddzielone zostały od siebie elementy niezależnej logiki. Efekt:

See the Pen
React Hooks: Własny hook useApi
by Michał Miszczyszyn (@mmiszy)
on CodePen.

Podsumowanie

To tylko kilka podstawowych hooków, zachęcam do tworzenia własnych. To mega proste 🙂 Pochwal się w komentarzu, jakie inne ciekawe hooki znasz!

Jeśli chcesz na bieżąco dowiadywać się o kolejnych częściach kursu React.js to koniecznie śledź mnie na Facebooku i zapisz się na newsletter.

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Nawigacja po kursie:
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments