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:
- Zostaje zmieniony currentPage na kolejny, przez co wiemy, że mamy wykonać zapytanie do API
- 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:
- Czy pierwszy artykuł ma inny tytuł? - jeśli tak, oznacza to, że mamy nową listę artykułów
- 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.