Moduł 3 - Zajęcia 6 - zapytania HTTP

W tym celu zastosujemy wiedzę, którą przyswoiliśmy w tym module.

Zaczniemy od rozbudowania stanu naszej aplikacji, oprócz dotychczasowych danych dodamy jeszcze informacje o obecnej stronie:

            state = {
              articles: [],
              isLoading: false,
              error: '',
              currentPage: 1
            }
          

Następnie przeniesiemy naszą funkcjonalność odpowiadającą za pobieranie nowych paczek z componentDidMount do osobnej funkcji - pozwoli nam to na reużycie jej także w componentDidUpdate

            async componentDidMount() {
              await this.getInitialData()
            }

            async componentDidUpdate() {
              await this.getInitialData()
            }

            getInitialData = async () => {
                const query = `react&page=${this.state.currentPage}`;
              try {
                const articles = await api.fetchArticlesWithQuery(query)
                    this.setState({ articles })
              } catch (error) {
                this.setState({ error })
              } finally {
                this.setState({ isLoading: false })
              }
            }
          

Jak widać poza stworzeniem funkcji getInitialData została ona delikatnie rozbudowana - oprócz informacji o wyszukiwaniu (react) podajemy także informację o konkretnej stronie (currentPage). Zgodnie z dokumentacją dostępną pod linkiem wyżej możemy dodać do zapytania dodatkowe informacje. Użycie currentPage pozwoli nam na pobranie kolejnych paczek danych.

Chcielibyśmy się także zabezpieczyć przed ciągłym re-renderem - ma to związek z tym, że nadpisujemy pole articles . Chcielibyśmy przerenderować komponent tylko w dwóch przypadkach:

  1. Zostaje zmieniony currentPage na kolejny, przez co wiemy, że mamy wykonać zapytanie do API
  2. Otrzymujemy odpowiedź z API z nową listą artykułów.

W związku z tym możemy wykorzystać shouldComponentUpdate

            shouldComponentUpdate(nextProps, nextState) {
              const oldState = this.state;

              if (nextState.articles[0]?.title === oldState.articles[0]?.title && 
                    nextState.currentPage === oldState.currentPage) {
                return false
              }

              return true
            }
          

Sprawdzamy tutaj dwie rzeczy:

  1. Czy pierwszy artykuł ma inny tytuł? - jeśli tak, oznacza to, że mamy nową listę artykułów
  2. Czy wartość currentPage uległa zmianie? - jeśli tak, oznacza to, że musimy przerenderować komponent by pobrać nowe dane

Dodajemy także przycisk pozwalający zmienić stronę

            <button onClick={this.handleClick}>
              Next page
            </button>
          

A także funkcję pozwalającą obsłużyć kliknięcie

            handleCurrentPageUpdate = () => {
              this.setState((state) => {
                return {
                  currentPage: state.currentPage + 1
                }
              })
            }

            handleClick = () => {
              this.handleCurrentPageUpdate()
            }
          

Aktualizację stanu naszego komponentu wynosimy do osobnej funkcji, przyda nam się to na kolejnym etapie.

Pozostał nam tylko jeden warunek do spełnienia: Co 10 sekund aplikacja samoczynnie poprosi o nową paczkę danych. Możemy to osiągnąć w następujący sposób.

Dodajemy zmienną timer:

            timer = null;
          

Modyfikujemy funkcję componentDidMount

            async componentDidMount() {
              this.timer = setInterval(() => {
                this.handleCurrentPageUpdate()
              }, 10000)
              await this.getInitialData()
            }
          

Oraz korzystamy z funkcji componentWillUnmount w celu zaprzestania działania setInterval po odmontowaniu komponentu

            componentWillUnmount() {
              clearInterval(this.timer);
            }
          

Jeżeli na środowisku deweloperskim wywoływana jest funkcja componentWillUnmount, a ten komponent jest nadal żywy i widoczny możemy zrobić drobną zmianę w pliku index.js i zakomentować <React.StrictMode></React.StrictMode>

            root.render(
              // <React.StrictMode>
                <App />
              // </React.StrictMode>
            );
          

Cały kod App.js

            import { Component } from 'react';
            import axios from "axios";
            import ContentLoader from 'react-content-loader';

            import './App.css';
            import ArticleList from './components/ArticleList';
            import api from './utils/getArticles';

            axios.defaults.baseURL = "https://hn.algolia.com/api/v1";
            class App extends Component {
              state = {
                articles: [],
                isLoading: false,
                error: '',
                currentPage: 1
              }

              timer = null;

              async componentDidMount() {
                this.timer = setInterval(() => {
                  this.handleCurrentPageUpdate()
                }, 10000)
                await this.getInitialData()
              }

              async componentDidUpdate() {
                await this.getInitialData()
              }

              getInitialData = async () => {
                const query = `react&page=${this.state.currentPage}`;
                try {
                  const articles = await api.fetchArticlesWithQuery(query)
                  this.setState({ articles })
                } catch (error) {
                  this.setState({ error })
                } finally {
                  this.setState({ isLoading: false })
                }
              }

              shouldComponentUpdate(nextProps, nextState) {
                const oldState = this.state;

                if (nextState.articles[0]?.title === oldState.articles[0]?.title 
                        && nextState.currentPage === oldState.currentPage) {
                  return false
                }

                return true
              }

              handleCurrentPageUpdate = () => {
                this.setState((state) => {
                  return {
                    currentPage: state.currentPage + 1
                  }
                })
              }

              handleClick = () => {
                this.handleCurrentPageUpdate()
              }

              componentWillUnmount() {
                clearInterval(this.timer);
              }

              render() {
                const { articles, isLoading, error } = this.state;

                return (
                  <>
                    {error && <p>Something went wrong: {error.message}</p>}
                    {isLoading && <ContentLoader />}
                    {articles.length > 0 && <ArticleList articles={articles} />}
                    <button onClick={this.handleClick}>
                      Next page
                    </button>
                  </>
                );
              }

            }

            export default App;
          

Jak widać metody HTTP oraz cyklu życia komponentu współgrają ze sobą na każdym kroku. Należy o tym pamiętać i używać ich razem, by osiągnąć możliwie najlepszy efekt.