React Hooks a żądania do API

Ten wpis jest 37 częścią z 39 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

React Hooks mocno upraszczają właściwie wszystko, co do tej pory robiłaś. W jednym z pierwszych odcinków kursu pokazywałem, jak można w szybki sposób pobierać dane z API używając fetch w Reakcie. Czy Hooki coś tutaj zmieniają? Ależ tak!

Fetch do tej pory

Zaczniemy może od przyjrzenia się, jak taki fetch wyglądał do tej pory w klasach:

export class App extends React.Component {
  state = {
    contacts: []
  };

  componentDidMount() {
    fetch("https://randomuser.me/api/?format=json&results=10")
      .then(res => res.json())
      .then(json => this.setState({ contacts: json.results }));
  }

  render() {
    return <ContactsList contacts={this.state.contacts} />;
  }
}

Co tu się dokładnie dzieje? Najpierw deklaruję state, następnie w componentDidMount wykonuję zapytanie do API, czekam na wynik i rezultat zapisuję do stanu. Ten kod pochodzi ze starszego wpisu, tam też znajdziesz więcej wyjaśnień:

Komunikacja z API w React.js

Nie wygląda skomplikowanie, ale czy może być jeszcze prostsze?

Wchodzą Hooki

Z użyciem znanych Ci już Hooków useStateuseEffect wygląda to tak:

function App() {
  const [contacts, setContacts] = useState([]);

  useEffect(() => {
    fetch("https://randomuser.me/api/?format=json&results=10")
      .then(res => res.json())
      .then(json => setContacts(json.results));
  }, []);

  return <ContactsList contacts={contacts} />
}

Zwróć uwagę na jedną ważną rzecz: Jako drugi argument do useEffect podałem tutaj pustą tablicę. Po co? Aby fetch nie wykonywał się przy każdym renderze, a tylko za pierwszym razem!

No ładnie, prawda? Ale wcale nie jest dużo krótsze. Dodajmy więc nowe wymaganie…

Aktualizacja wyników gdy zmienia się ID

Załóżmy, że Twój komponent ma wyświetlać dane pobrane z API dla danego ID przekazanego mu jako props. W użyciu:

<App id={…} />

Gdy id się zmieni, komponent ma pobrać dane na nowo i wyświetlić. Jak to wygląda w klasie?

export class App extends React.Component {
  state = {
    contacts: []
  };

  fetchData() {
    fetch(`https://randomuser.me/api/?format=json&results=10&seed=${this.props.id}`)
      .then(res => res.json())
      .then(json => this.setState({ contacts: json.results }));
  }

  componentDidMount() {
    this.fetchData();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData();
    }
  }

  render() {
    return <ContactsList contacts={this.state.contacts} />;
  }
}

Łołoło, trochę nam ten komponent urósł 🤔 A z hookami?

function App({ id }) {
  const [contacts, setContacts] = useState([]);

  useEffect(() => {
    fetch(`https://randomuser.me/api/?format=json&results=10&seed=${id}`)
      .then(res => res.json())
      .then(json => setContacts(json.results));
  }, [id]);

  return <ContactsList contacts={contacts} />
}

To jest nadal tak samo krótkie, jak poprzednio. Zmieniły się tylko 3 fragmenty kodu: Props jako argument do komponentu, fetch pod inny adres i tablica jako drugi argument do useEffect już nie jest pusta: [id].

W tym kodzie jest bug

Jest subtelny i pewnie się na niego nie natkniesz, ale uwierz mi, że w tym kodzie jest bug. Gdy szybko zmienisz wartość ID, wykonają się w krótkim czasie dwa żądania do API. A co się stanie, jeśli odpowiedź na pierwsze żądanie przyjdzie później niż na drugie? Komponent wyświetli nieaktualne dane. Bug!

Aby go naprawić, trzeba anulować poprzednie żądanie. Jak to wygląda w klasie?

export class App extends React.Component {
  state = {
    contacts: []
  };

  controller = new AbortController();

  fetchData() {
    this.controller.abort();
    fetch(`https://randomuser.me/api/?format=json&results=10&seed=${this.props.id}`, { signal: this.controller.signal })
      .then(res => res.json())
      .then(json => this.setState({ contacts: json.results }));
  }

  componentDidMount() {
    this.fetchData();
  }

  componentWillUnmount() {
    this.controller.abort();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData();
    }
  }

  render() {
    return <ContactsList contacts={this.state.contacts} />;
  }
}

Używam tutaj eksperymentalnego AbortController, więc przestudiuj ten kod dokładnie 🙂 Aby anulować żądanie, wywołuję controller.abort() — w klasie musiałem sobie zapisać instancję AbortController.

Dodatkowo dopisałem też brakujące componentWillUnmount, aby komponent nie pobierał niepotrzebnie danych, gdy już zostanie odmontowany.

Z Hookami jest dużo prościej, bo korzystasz z naturalnego dla JS domknięcia:

function App({ id }) {
  const [contacts, setContacts] = useState([]);

  useEffect(() => {
    const controller = new AbortController();
    fetch(`https://randomuser.me/api/?format=json&results=10&seed=${id}`, { signal: controller.signal })
      .then(res => res.json())
      .then(json => setContacts(json.results));

    return () => controller.abort();
  }, [id]);

  return <ContactsList contacts={contacts} />
}

Tak, to już!

Hooki są czystsze

Zwróć uwagę jak bardo porozrzucany jest kod w ostatnim przykładzie w klasie — constructor, componentDidMount, componentWillUnmountcomponentDidUpdate. W hookach zaś, wszystko jest obok siebie. Powiązany kod leży blisko.

Dodatkowo, łatwo mogłabyś się pokusić o stworzenie nowego hooka, który by całą tę logikę enkapsulował. Dzięki temu mogłabyś łatwo go używać w różnych miejscach, bez konieczności duplikowania kodu. To w praktyce niemożliwe przy użyciu klas (chyba, że przez HoC lub zagnieżdżając kolejne komponenty…)

Pytania?

Sprawdź szkolenia Type of Web z React! Jeśli chcesz na bieżąco śledzić kolejne części kursu React.js to koniecznie polub mnie na Facebooku i zapisz się na newsletter.

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