Zastanówmy się w jaki sposób możemy lekko odkurzyć nasz komponent napisany w React, aby pewną cześć logiki związaną z pobieraniem danych wydzielić do oddzielnego pliku i zapewnić ponowną używalność kodu…
Dlaczego warto stosować takie podejście?
Przede wszystkim zapewniamy podział kodu na pewne obszary, nie mieszamy logiki wyświetlania danych z pobieraniem danych. Zapewniamy możliwość ponownego użycia danego fragmentu kodu. Dodatkowo też, wymuszenie trzymania się pewnych zasad w ramach zespołu złożonego z kilku developerów, pozwoli na utrzymanie czystszego kodu…
Na początek zacznijmy od stworzenia aplikacji:
create-react-app api_call cd api_call
W naszym przypadku żądania do API będziemy wykonywać poprzez bibliotekę axios, którą należy zainstalować.
npm install axios
Teraz pozostaje nam uruchomić aplikację
npm start
Dane będziemy pobierać z otwartego API:
https://pokeapi.co/
Stwórzmy komponent, który będzie pobierał dane i je wyświetlał…
import React from 'react';
import axios from 'axios/index';
class Pokemon extends React.Component {
state = {
pokemonList: null
}
componentDidMount() {
// fetch data from API
}
render() {
if (this.state.pokemonList === null) {
return (<div>Loading...</div>);
}
return (
<div>
Display pokemon list...
</div>
);
}
}
export default Pokemon;
Kolejnym krokiem musi być przygotowanie żądania do API, które pobierze dane i przypisze je do state pokemonList. W tym przykładzie pomijamy w ogóle obsługę błędów, bo ten element będzie na pewno poruszony w innym artykule.
Modyfikujemy zawartość componentDidMount()
componentDidMount() {
// In real life, API URL should be declared in another file :)
axios.get('https://pokeapi.co/api/v2/ability/')
.then((response) => {
this.setState(() => ({
pokemonList: response.data
}));
})
.catch((error) => {
// error handling
});
}
Musimy jeszcze wyświetlić listę, więc w metodzie render() dodajemy prosty Array.map() https://developer.mozilla.org/pl/docs/Web/JavaScript/Referencje/Obiekty/Array/map
render() {
if (this.state.pokemonList === null) {
return (<div>Loading...</div>);
}
return (
<div>
{
this.state.pokemonList.results.map((pokemon) => {
return (
<div name={pokemon.name}>{pokemon.name}</div>
)
})
}
</div>
);
}
Ok. Wszystko działa, komponent pobiera dane, lista jest renderowana. Mamy tutaj jednak sytuację, w której komponent zarówno pobiera dane jak i wyświetla je.
Czas na wydzielenie logiki związanej z pobieraniem danych do oddzielnego pliku.
Struktura folderów i nazewnictwo
Na pewno struktura folderów będzie tematem oddzielnego artykułu, jednak teraz skupimy się na czymś zupełnie innym.
W naszym prostym przykładzie utworzymy folder api, a w nim plik PokemonAPI.js
Przenosimy teraz część, zadeklarowaną w componentDidMount() to naszego pliku PokemonAPI.js, który będzie wyglądał tak:
import axios from 'axios';
export const getPokemonList = () => {
// UWAGA! Konfiguracja URL do API powinna byc wyniesiona
// do zewnetrznego pliku...
return axios.get('https://pokeapi.co/api/v2/ability/')
.then((response) => {
return response.data;
})
.catch((error) => {
return error;
});
}
W pliku Pokemon.js, który jest naszym komponentem importujemy plik z API
import * as PokemonAPI from '../api/PokemonAPI';
i w componentDidMount() odwołujemy się do metody getPokemonList() poprzez
PokemonAPI.getPokemonList()
W związku że axios.get()jest asynchroniczne i zwraca nam Promise, bezpośrednie przypisanie danych zwróconych przez getPokemonList() poprzez setState() nie zadziała.
// to nie działa, ponieważ getPokemonList() zwraca Promise
this.setState(() => ({
pokemonList: PokemonAPI.getPokemonList()
}));
Z pomocą przychodzi nam jednak mechanizm Promise chaining ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#Chaining), który umożliwia wywołanie kilka razy metody .then(), które nastąpią po sobie (oczywiście jeżeli nie wystąpi błąd).
Metoda getPokemonList() zawiera w sobie fragment .then() – zdefiniowany w PokemonAPI.js, który zostanie wykonany po otrzymaniu poprawnej odpowiedzi z serwera.
Następnie zostanie wywołana metoda .then() z pliku Pokemon.js, w metodzie componentDidMount() i dane zostaną przypisane do state.
componentDidMount() {
PokemonAPI.getPokemonList()
.then((pokemonList) => {
this.setState(() => ({
pokemonList: pokemonList
}));
});
}
Innym sposobem, aby uniknąć ponownego .then(), będzie użycie async/await, ale to zostawiam na później… 🙂
Jak widzicie logika związana z pobieraniem danych z API została wydzielona do zewnętrznego pliku i teraz w całej aplikacji możemy z niej korzystać w wielu miejscach.
Za chwilę ktoś pewnie powie, że warto jeszcze posprzątać komponent, bowiem w metodzie render() występuje .map() i to też można przenieść do innego komponentu…
Tak, zgadza się. W następnym artykule dotyczącym React, skupimy się na czymś, co nazywa się Container Component – link do artykułu: https://www.magicweb.pl/programowanie/frontend/react/container-component/
Repozytorium
Mimo, że projekt nie jest skomplikowany, możecie zobaczyć jak wygląda to finalnie.
https://github.com/magicwebpl/react-api-call-external-file
Zadanie domowe
Przygotuj proszę inny request do API, który zostanie umieszczony w oddzielnym pliku, a następnie wykorzystany w kilku komponentach. Dodatkowo możesz pomyśleć o obsłudze błędów, wykorzystując do tego .catch() oraz Promise chaining przedstawiony przy okazji getPokemonList() oraz .then(). Możesz też zapoznać się z async/await, aby uniknąć ponownego .then() i bezpośrednio po wywołaniu metody z pliku API, przypisać dane.
Źródła:
Obraz: https://unsplash.com/photos/ahi73ZN5P0Y