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